@objectstack/runtime 7.2.1 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1169 -2155
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -18
- package/dist/index.d.ts +112 -18
- package/dist/index.js +1135 -2121
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -32,13 +32,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
34
|
// src/load-artifact-bundle.ts
|
|
35
|
-
var load_artifact_bundle_exports = {};
|
|
36
|
-
__export(load_artifact_bundle_exports, {
|
|
37
|
-
isHttpUrl: () => isHttpUrl,
|
|
38
|
-
loadArtifactBundle: () => loadArtifactBundle,
|
|
39
|
-
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
40
|
-
readArtifactSource: () => readArtifactSource
|
|
41
|
-
});
|
|
42
35
|
function isHttpUrl(pathOrUrl) {
|
|
43
36
|
return /^https?:\/\//i.test(pathOrUrl);
|
|
44
37
|
}
|
|
@@ -294,17 +287,37 @@ var init_seed_loader = __esm({
|
|
|
294
287
|
}
|
|
295
288
|
const objectRefs = refMap.get(objectName) || [];
|
|
296
289
|
const seedNow = /* @__PURE__ */ new Date();
|
|
290
|
+
const seedIdentity = config.identity;
|
|
291
|
+
const baseEvalCtx = {
|
|
292
|
+
now: seedNow,
|
|
293
|
+
user: seedIdentity?.user,
|
|
294
|
+
// Fall back to the per-tenant organizationId so `os.org.id` resolves
|
|
295
|
+
// during per-org replay even without an explicit identity.org.
|
|
296
|
+
org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
|
|
297
|
+
env: config.env
|
|
298
|
+
};
|
|
297
299
|
for (let i = 0; i < dataset.records.length; i++) {
|
|
298
300
|
const seedResult = (0, import_formula.resolveSeedRecord)(
|
|
299
301
|
dataset.records[i],
|
|
300
|
-
|
|
302
|
+
baseEvalCtx
|
|
301
303
|
);
|
|
302
|
-
const record = seedResult.ok ? { ...seedResult.value } : { ...dataset.records[i] };
|
|
303
304
|
if (!seedResult.ok) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
errored++;
|
|
306
|
+
const error = {
|
|
307
|
+
sourceObject: objectName,
|
|
308
|
+
field: "(expression)",
|
|
309
|
+
targetObject: objectName,
|
|
310
|
+
targetField: "(expression)",
|
|
311
|
+
attemptedValue: dataset.records[i],
|
|
312
|
+
recordIndex: i,
|
|
313
|
+
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}. Records using cel\`os.user.id\` / cel\`os.org.id\` require a seed identity \u2014 ensure a system/admin user exists before seeding (see SeedLoaderConfig.identity).`
|
|
314
|
+
};
|
|
315
|
+
errors.push(error);
|
|
316
|
+
allErrors.push(error);
|
|
317
|
+
this.logger.warn(`[SeedLoader] ${error.message}`);
|
|
318
|
+
continue;
|
|
307
319
|
}
|
|
320
|
+
const record = { ...seedResult.value };
|
|
308
321
|
if (config.organizationId && record["organization_id"] == null) {
|
|
309
322
|
record["organization_id"] = config.organizationId;
|
|
310
323
|
}
|
|
@@ -383,10 +396,18 @@ var init_seed_loader = __esm({
|
|
|
383
396
|
}
|
|
384
397
|
} catch (err) {
|
|
385
398
|
errored++;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
399
|
+
const error = {
|
|
400
|
+
sourceObject: objectName,
|
|
401
|
+
field: "(write)",
|
|
402
|
+
targetObject: objectName,
|
|
403
|
+
targetField: externalId,
|
|
404
|
+
attemptedValue: record[externalId] ?? null,
|
|
405
|
+
recordIndex: i,
|
|
406
|
+
message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
|
|
407
|
+
};
|
|
408
|
+
errors.push(error);
|
|
409
|
+
allErrors.push(error);
|
|
410
|
+
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
390
411
|
}
|
|
391
412
|
} else {
|
|
392
413
|
const externalIdValue = String(record[externalId] ?? "");
|
|
@@ -726,6 +747,52 @@ var init_seed_loader = __esm({
|
|
|
726
747
|
}
|
|
727
748
|
});
|
|
728
749
|
|
|
750
|
+
// src/package-state-store.ts
|
|
751
|
+
function sanitizeEnvironmentId(environmentId) {
|
|
752
|
+
const raw = (environmentId ?? process.env.OS_ENVIRONMENT_ID ?? DEFAULT_ENVIRONMENT_ID).trim();
|
|
753
|
+
const safe = raw.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
754
|
+
return safe.length > 0 ? safe : DEFAULT_ENVIRONMENT_ID;
|
|
755
|
+
}
|
|
756
|
+
function stateFilePath(environmentId) {
|
|
757
|
+
return (0, import_node_path2.join)(resolveObjectStackHome(), "package-state", `${sanitizeEnvironmentId(environmentId)}.json`);
|
|
758
|
+
}
|
|
759
|
+
function readState(environmentId) {
|
|
760
|
+
const file = stateFilePath(environmentId);
|
|
761
|
+
if (!(0, import_node_fs.existsSync)(file)) return {};
|
|
762
|
+
try {
|
|
763
|
+
const parsed = JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
|
|
764
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
765
|
+
} catch {
|
|
766
|
+
return {};
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function writeState(environmentId, state) {
|
|
770
|
+
const file = stateFilePath(environmentId);
|
|
771
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(file), { recursive: true });
|
|
772
|
+
(0, import_node_fs.writeFileSync)(file, `${JSON.stringify(state, null, 2)}
|
|
773
|
+
`, "utf8");
|
|
774
|
+
}
|
|
775
|
+
function loadDisabledPackageIds(environmentId) {
|
|
776
|
+
const disabled = readState(environmentId).disabled;
|
|
777
|
+
return new Set(Array.isArray(disabled) ? disabled.filter((id) => typeof id === "string") : []);
|
|
778
|
+
}
|
|
779
|
+
function setPackageDisabled(environmentId, packageId, disabled) {
|
|
780
|
+
const ids = loadDisabledPackageIds(environmentId);
|
|
781
|
+
if (disabled) ids.add(packageId);
|
|
782
|
+
else ids.delete(packageId);
|
|
783
|
+
writeState(environmentId, { disabled: Array.from(ids).sort() });
|
|
784
|
+
}
|
|
785
|
+
var import_node_fs, import_node_path2, DEFAULT_ENVIRONMENT_ID;
|
|
786
|
+
var init_package_state_store = __esm({
|
|
787
|
+
"src/package-state-store.ts"() {
|
|
788
|
+
"use strict";
|
|
789
|
+
import_node_fs = require("fs");
|
|
790
|
+
import_node_path2 = require("path");
|
|
791
|
+
init_standalone_stack();
|
|
792
|
+
DEFAULT_ENVIRONMENT_ID = "default";
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
729
796
|
// src/sandbox/quickjs-runner.ts
|
|
730
797
|
function installApiMethod(vm, parent, method, objectName, ctx, caps, required, origin) {
|
|
731
798
|
const fn = vm.newAsyncifiedFunction(method, async (...argHandles) => {
|
|
@@ -1284,12 +1351,14 @@ function collectBundleFunctions(bundle) {
|
|
|
1284
1351
|
merge(bundle?.manifest?.functions);
|
|
1285
1352
|
return out;
|
|
1286
1353
|
}
|
|
1287
|
-
var import_types, AppPlugin;
|
|
1354
|
+
var import_types, import_system, AppPlugin;
|
|
1288
1355
|
var init_app_plugin = __esm({
|
|
1289
1356
|
"src/app-plugin.ts"() {
|
|
1290
1357
|
"use strict";
|
|
1291
1358
|
import_types = require("@objectstack/types");
|
|
1292
1359
|
init_seed_loader();
|
|
1360
|
+
init_package_state_store();
|
|
1361
|
+
import_system = require("@objectstack/spec/system");
|
|
1293
1362
|
init_quickjs_runner();
|
|
1294
1363
|
init_body_runner();
|
|
1295
1364
|
AppPlugin = class {
|
|
@@ -1315,6 +1384,24 @@ var init_app_plugin = __esm({
|
|
|
1315
1384
|
console.warn(
|
|
1316
1385
|
`[AppPlugin:init] appId=${appId} keys=${Object.keys(servicePayload).join(",")} flows=${Array.isArray(servicePayload.flows) ? servicePayload.flows.length : "n/a"}`
|
|
1317
1386
|
);
|
|
1387
|
+
try {
|
|
1388
|
+
const ql = ctx.getService("objectql");
|
|
1389
|
+
const setter = ql?.registry?.setInitialDisabledPackageIds;
|
|
1390
|
+
if (typeof setter === "function") {
|
|
1391
|
+
const disabled = loadDisabledPackageIds(this.projectContext?.environmentId);
|
|
1392
|
+
if (disabled.size > 0) {
|
|
1393
|
+
setter.call(ql.registry, disabled);
|
|
1394
|
+
ctx.logger.info("[AppPlugin] seeded persisted disabled packages", {
|
|
1395
|
+
environmentId: this.projectContext?.environmentId,
|
|
1396
|
+
disabled: Array.from(disabled)
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
ctx.logger.warn("[AppPlugin] failed to seed persisted package state", {
|
|
1402
|
+
error: err?.message ?? String(err)
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1318
1405
|
ctx.getService("manifest").register(servicePayload);
|
|
1319
1406
|
};
|
|
1320
1407
|
this.start = async (ctx) => {
|
|
@@ -1346,6 +1433,27 @@ var init_app_plugin = __esm({
|
|
|
1346
1433
|
});
|
|
1347
1434
|
ql.setDatasourceMapping(this.bundle.datasourceMapping);
|
|
1348
1435
|
}
|
|
1436
|
+
try {
|
|
1437
|
+
const dsDefs = this.bundle.datasources;
|
|
1438
|
+
const dsList = Array.isArray(dsDefs) ? dsDefs : dsDefs && typeof dsDefs === "object" ? Object.entries(dsDefs).map(([name, def]) => ({ name, ...def })) : [];
|
|
1439
|
+
if (dsList.length > 0) {
|
|
1440
|
+
const metadata = ctx.getService("metadata");
|
|
1441
|
+
if (typeof metadata?.registerInMemory === "function") {
|
|
1442
|
+
for (const ds of dsList) {
|
|
1443
|
+
if (!ds?.name) continue;
|
|
1444
|
+
metadata.registerInMemory("datasource", ds.name, { ...ds, origin: "code" });
|
|
1445
|
+
}
|
|
1446
|
+
ctx.logger.info("Registered code-defined datasources in metadata registry", {
|
|
1447
|
+
appId,
|
|
1448
|
+
count: dsList.length
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
ctx.logger.warn("[AppPlugin] failed to register code-defined datasources", {
|
|
1454
|
+
error: err?.message ?? String(err)
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1349
1457
|
const stackBundle = this.bundle.default || this.bundle;
|
|
1350
1458
|
const runtime = stackBundle && typeof stackBundle.onEnable === "function" ? stackBundle : this.bundle;
|
|
1351
1459
|
if (runtime && typeof runtime.onEnable === "function") {
|
|
@@ -1440,49 +1548,6 @@ var init_app_plugin = __esm({
|
|
|
1440
1548
|
appId
|
|
1441
1549
|
});
|
|
1442
1550
|
}
|
|
1443
|
-
try {
|
|
1444
|
-
const approvals = Array.isArray(this.bundle.approvals) ? this.bundle.approvals : Array.isArray((this.bundle.manifest || {}).approvals) ? this.bundle.manifest.approvals : [];
|
|
1445
|
-
if (approvals.length > 0) {
|
|
1446
|
-
ctx.hook("kernel:ready", async () => {
|
|
1447
|
-
let svc;
|
|
1448
|
-
try {
|
|
1449
|
-
svc = ctx.getService("approvals");
|
|
1450
|
-
} catch {
|
|
1451
|
-
}
|
|
1452
|
-
if (!svc || typeof svc.defineProcess !== "function") {
|
|
1453
|
-
ctx.logger.warn("[AppPlugin] approvals service not registered \u2014 skipping declarative processes", {
|
|
1454
|
-
appId,
|
|
1455
|
-
processCount: approvals.length
|
|
1456
|
-
});
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
const sysCtx = { isSystem: true, roles: [], permissions: [] };
|
|
1460
|
-
let ok = 0;
|
|
1461
|
-
for (const proc of approvals) {
|
|
1462
|
-
try {
|
|
1463
|
-
await svc.defineProcess({
|
|
1464
|
-
name: proc.name,
|
|
1465
|
-
label: proc.label,
|
|
1466
|
-
object: proc.object,
|
|
1467
|
-
description: proc.description,
|
|
1468
|
-
active: proc.active !== false,
|
|
1469
|
-
definition: proc
|
|
1470
|
-
}, sysCtx);
|
|
1471
|
-
ok++;
|
|
1472
|
-
} catch (err) {
|
|
1473
|
-
ctx.logger.warn("[AppPlugin] Failed to register approval process", {
|
|
1474
|
-
appId,
|
|
1475
|
-
process: proc?.name,
|
|
1476
|
-
error: err?.message ?? String(err)
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
ctx.logger.info("[AppPlugin] Registered approval processes", { appId, count: ok });
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
} catch (err) {
|
|
1484
|
-
ctx.logger.error("[AppPlugin] Failed to schedule approval-process registration", err, { appId });
|
|
1485
|
-
}
|
|
1486
1551
|
try {
|
|
1487
1552
|
const jobs = Array.isArray(this.bundle.jobs) ? this.bundle.jobs : Array.isArray((this.bundle.manifest || {}).jobs) ? this.bundle.manifest.jobs : [];
|
|
1488
1553
|
if (jobs.length > 0) {
|
|
@@ -1559,6 +1624,7 @@ var init_app_plugin = __esm({
|
|
|
1559
1624
|
...d,
|
|
1560
1625
|
object: d.object
|
|
1561
1626
|
}));
|
|
1627
|
+
const seedIdentity = await this.ensureSeedIdentity(ql, ctx.logger);
|
|
1562
1628
|
try {
|
|
1563
1629
|
const kernel = ctx.kernel;
|
|
1564
1630
|
const existing = (() => {
|
|
@@ -1600,7 +1666,12 @@ var init_app_plugin = __esm({
|
|
|
1600
1666
|
config: {
|
|
1601
1667
|
defaultMode: "upsert",
|
|
1602
1668
|
multiPass: true,
|
|
1603
|
-
organizationId
|
|
1669
|
+
organizationId,
|
|
1670
|
+
// Bind os.user (system identity) and os.org (this
|
|
1671
|
+
// tenant) so identity-derived seed values resolve
|
|
1672
|
+
// per-org. org.id falls back to organizationId
|
|
1673
|
+
// inside the loader when identity.org is absent.
|
|
1674
|
+
identity: seedIdentity
|
|
1604
1675
|
}
|
|
1605
1676
|
});
|
|
1606
1677
|
const result = await seedLoader.load(request);
|
|
@@ -1628,14 +1699,34 @@ var init_app_plugin = __esm({
|
|
|
1628
1699
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1629
1700
|
const request = SeedLoaderRequestSchema.parse({
|
|
1630
1701
|
datasets: normalizedDatasets,
|
|
1631
|
-
config: { defaultMode: "upsert", multiPass: true }
|
|
1702
|
+
config: { defaultMode: "upsert", multiPass: true, identity: seedIdentity }
|
|
1632
1703
|
});
|
|
1633
1704
|
const result = await seedLoader.load(request);
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1705
|
+
const { totalInserted, totalUpdated, totalSkipped, totalErrored } = result.summary;
|
|
1706
|
+
if (result.success) {
|
|
1707
|
+
ctx.logger.info("[Seeder] Seed loading complete", {
|
|
1708
|
+
inserted: totalInserted,
|
|
1709
|
+
updated: totalUpdated,
|
|
1710
|
+
skipped: totalSkipped,
|
|
1711
|
+
errored: totalErrored
|
|
1712
|
+
});
|
|
1713
|
+
} else {
|
|
1714
|
+
ctx.logger.warn(
|
|
1715
|
+
`[Seeder] Seed loading completed with ${totalErrored} dropped record(s) and ${result.errors.length} error(s) for ${appId}`,
|
|
1716
|
+
{
|
|
1717
|
+
inserted: totalInserted,
|
|
1718
|
+
updated: totalUpdated,
|
|
1719
|
+
skipped: totalSkipped,
|
|
1720
|
+
errored: totalErrored
|
|
1721
|
+
}
|
|
1722
|
+
);
|
|
1723
|
+
for (const e of result.errors.slice(0, 20)) {
|
|
1724
|
+
ctx.logger.warn(`[Seeder] \u2717 ${e.message}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (result.errors.length > 20) {
|
|
1727
|
+
ctx.logger.warn(`[Seeder] \u2026and ${result.errors.length - 20} more error(s)`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1639
1730
|
} else {
|
|
1640
1731
|
ctx.logger.debug("[Seeder] No metadata service; using basic insert fallback");
|
|
1641
1732
|
for (const dataset of normalizedDatasets) {
|
|
@@ -1737,6 +1828,64 @@ var init_app_plugin = __esm({
|
|
|
1737
1828
|
this.name = `plugin.app.${appId}`;
|
|
1738
1829
|
this.version = sys?.version;
|
|
1739
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Resolve the identity bound to `os.user` / `os.org` for seed CEL values.
|
|
1833
|
+
*
|
|
1834
|
+
* On a fresh boot there are zero users until the first human sign-up
|
|
1835
|
+
* (which the SeedLoader runs *before*), so identity-derived seeds like
|
|
1836
|
+
* `owner_id: cel`os.user.id`` had nothing to resolve against and were
|
|
1837
|
+
* dropped silently. To make seeds deterministic and self-sufficient we
|
|
1838
|
+
* upsert a single non-loginable **system user** (`usr_system`) and bind
|
|
1839
|
+
* it as `os.user`.
|
|
1840
|
+
*
|
|
1841
|
+
* Why a dedicated system user rather than the login admin:
|
|
1842
|
+
* - `sys_user` is better-auth-managed and schema-locked (ADR-0010); the
|
|
1843
|
+
* password lives in `sys_account`, so a *loginable* admin can only be
|
|
1844
|
+
* minted through better-auth (the CLI does this via HTTP sign-up after
|
|
1845
|
+
* boot). A raw insert here would bypass those invariants.
|
|
1846
|
+
* - `usr_system` is an owner identity only (no credential row), analogous
|
|
1847
|
+
* to Salesforce's "Automated Process" user. The human admin is created
|
|
1848
|
+
* independently and need not be the seed owner.
|
|
1849
|
+
*
|
|
1850
|
+
* Idempotent: matches by the stable id, inserts once, reuses thereafter.
|
|
1851
|
+
* Failures are non-fatal (logged) — records that actually need `os.user`
|
|
1852
|
+
* then fail loudly in the loader with an actionable message.
|
|
1853
|
+
*/
|
|
1854
|
+
async ensureSeedIdentity(ql, logger) {
|
|
1855
|
+
const SYSTEM_USER_ID = import_system.SystemUserId.SYSTEM;
|
|
1856
|
+
const SYSTEM_USER_EMAIL = "system@objectstack.local";
|
|
1857
|
+
const identity = { user: { id: SYSTEM_USER_ID, role: "system", email: SYSTEM_USER_EMAIL } };
|
|
1858
|
+
const opts = { context: { isSystem: true } };
|
|
1859
|
+
try {
|
|
1860
|
+
const existing = await ql.find(
|
|
1861
|
+
"sys_user",
|
|
1862
|
+
{ where: { id: SYSTEM_USER_ID }, limit: 1 },
|
|
1863
|
+
opts
|
|
1864
|
+
);
|
|
1865
|
+
if (Array.isArray(existing) && existing.length > 0) {
|
|
1866
|
+
return identity;
|
|
1867
|
+
}
|
|
1868
|
+
await ql.insert(
|
|
1869
|
+
"sys_user",
|
|
1870
|
+
{
|
|
1871
|
+
id: SYSTEM_USER_ID,
|
|
1872
|
+
name: "System",
|
|
1873
|
+
email: SYSTEM_USER_EMAIL,
|
|
1874
|
+
email_verified: true,
|
|
1875
|
+
role: "system"
|
|
1876
|
+
},
|
|
1877
|
+
opts
|
|
1878
|
+
);
|
|
1879
|
+
logger.info(
|
|
1880
|
+
`[Seeder] Provisioned deterministic system user (${SYSTEM_USER_ID}) as seed owner \u2014 binds os.user for identity-derived seed values`
|
|
1881
|
+
);
|
|
1882
|
+
} catch (err) {
|
|
1883
|
+
logger.warn("[Seeder] Failed to ensure system seed user; os.user-dependent seeds may be dropped", {
|
|
1884
|
+
error: err?.message ?? String(err)
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
return identity;
|
|
1888
|
+
}
|
|
1740
1889
|
/**
|
|
1741
1890
|
* Emit a kernel hook so the control-plane `AppCatalogService` can
|
|
1742
1891
|
* upsert / delete the corresponding `sys_app` row. Silently no-ops
|
|
@@ -1859,209 +2008,167 @@ var init_app_plugin = __esm({
|
|
|
1859
2008
|
}
|
|
1860
2009
|
});
|
|
1861
2010
|
|
|
1862
|
-
// src/
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
hashPlatformSsoClientSecret: () => hashPlatformSsoClientSecret,
|
|
1871
|
-
seedPlatformSsoClient: () => seedPlatformSsoClient
|
|
1872
|
-
});
|
|
1873
|
-
function derivePlatformSsoClientId(environmentId) {
|
|
1874
|
-
return `project_${environmentId}`;
|
|
1875
|
-
}
|
|
1876
|
-
function derivePlatformSsoClientSecret(baseSecret, environmentId) {
|
|
1877
|
-
return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
|
|
2011
|
+
// src/standalone-stack.ts
|
|
2012
|
+
function resolveObjectStackHome() {
|
|
2013
|
+
const raw = process.env.OS_HOME?.trim();
|
|
2014
|
+
if (raw && raw.length > 0) {
|
|
2015
|
+
if (raw.startsWith("~")) return (0, import_node_path3.resolve)((0, import_node_os.homedir)(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
2016
|
+
return (0, import_node_path3.resolve)(raw);
|
|
2017
|
+
}
|
|
2018
|
+
return (0, import_node_path3.resolve)((0, import_node_os.homedir)(), ".objectstack");
|
|
1878
2019
|
}
|
|
1879
|
-
function
|
|
1880
|
-
|
|
2020
|
+
function detectDriverFromUrl(dbUrl) {
|
|
2021
|
+
if (/^memory:\/\//i.test(dbUrl)) return "memory";
|
|
2022
|
+
if (/^(postgres(ql)?|pg):\/\//i.test(dbUrl)) return "postgres";
|
|
2023
|
+
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
2024
|
+
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
2025
|
+
if (/\.wasm\.db$/i.test(dbUrl)) return "sqlite-wasm";
|
|
2026
|
+
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
2027
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
2028
|
+
throw new Error(
|
|
2029
|
+
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
2030
|
+
);
|
|
1881
2031
|
}
|
|
1882
|
-
function
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2032
|
+
async function createStandaloneStack(config) {
|
|
2033
|
+
const cfg = StandaloneStackConfigSchema.parse(config ?? {});
|
|
2034
|
+
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
2035
|
+
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
2036
|
+
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
2037
|
+
const { AppPlugin: AppPlugin2 } = await Promise.resolve().then(() => (init_app_plugin(), app_plugin_exports));
|
|
2038
|
+
const cwd = process.cwd();
|
|
2039
|
+
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2040
|
+
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path3.resolve)(cwd, "dist/objectstack.json");
|
|
2041
|
+
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : (0, import_node_path3.resolve)(cwd, artifactPathInput);
|
|
2042
|
+
const dbUrl = cfg.databaseUrl ?? (0, import_types2.readEnvWithDeprecation)("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${(0, import_node_path3.resolve)(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
2043
|
+
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2044
|
+
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
2045
|
+
let driverPlugin;
|
|
2046
|
+
if (dbDriver === "memory") {
|
|
2047
|
+
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2048
|
+
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2049
|
+
} else if (dbDriver === "postgres") {
|
|
2050
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2051
|
+
driverPlugin = new DriverPlugin2(
|
|
2052
|
+
new SqlDriver({
|
|
2053
|
+
client: "pg",
|
|
2054
|
+
connection: dbUrl,
|
|
2055
|
+
pool: { min: 0, max: 5 }
|
|
2056
|
+
})
|
|
2057
|
+
);
|
|
2058
|
+
} else if (dbDriver === "mongodb") {
|
|
2059
|
+
let MongoDBDriver;
|
|
2060
|
+
try {
|
|
2061
|
+
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
2062
|
+
} catch (err) {
|
|
2063
|
+
throw new Error(
|
|
2064
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
2068
|
+
} else if (dbDriver === "sqlite-wasm") {
|
|
2069
|
+
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
2070
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
2071
|
+
if (filename && filename !== ":memory:") {
|
|
2072
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
2073
|
+
}
|
|
2074
|
+
driverPlugin = new DriverPlugin2(
|
|
2075
|
+
new SqliteWasmDriver({
|
|
2076
|
+
filename: filename || ":memory:",
|
|
2077
|
+
persist: filename && filename !== ":memory:" ? "on-write" : void 0
|
|
2078
|
+
})
|
|
2079
|
+
);
|
|
1890
2080
|
} else {
|
|
1891
|
-
|
|
2081
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2082
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
2083
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
2084
|
+
throw new Error(
|
|
2085
|
+
`[StandaloneStack] sqlite driver was selected but the URL does not look like a file path: "${dbUrl}". Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.`
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.resolve)(filename, ".."), { recursive: true });
|
|
2089
|
+
driverPlugin = new DriverPlugin2(
|
|
2090
|
+
new SqlDriver({
|
|
2091
|
+
client: "better-sqlite3",
|
|
2092
|
+
connection: { filename },
|
|
2093
|
+
useNullAsDefault: true
|
|
2094
|
+
})
|
|
2095
|
+
);
|
|
1892
2096
|
}
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
2097
|
+
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
2098
|
+
tag: "[StandaloneStack]",
|
|
2099
|
+
unwrapEnvelope: true
|
|
2100
|
+
});
|
|
2101
|
+
if (artifactBundle) {
|
|
2102
|
+
const flowsCount = Array.isArray(artifactBundle?.flows) ? artifactBundle.flows.length : "n/a";
|
|
2103
|
+
console.warn(
|
|
2104
|
+
`[StandaloneStack] artifact loaded: path=${artifactPath} keys=${Object.keys(artifactBundle).join(",")} flows=${flowsCount}`
|
|
2105
|
+
);
|
|
1902
2106
|
}
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2107
|
+
const plugins = [
|
|
2108
|
+
driverPlugin,
|
|
2109
|
+
new MetadataPlugin({
|
|
2110
|
+
// Source-file scanner OFF — declarative metadata is loaded
|
|
2111
|
+
// from the compiled artifact, not from yaml/json files on
|
|
2112
|
+
// disk. Scanning would also recursively watch the project
|
|
2113
|
+
// root (incl. node_modules), which is expensive and prone
|
|
2114
|
+
// to EMFILE.
|
|
2115
|
+
watch: false,
|
|
2116
|
+
// Artifact-file HMR ON in non-production so edits to
|
|
2117
|
+
// `*.view.ts` / `*.flow.ts` (which the CLI dev-mode watcher
|
|
2118
|
+
// recompiles into `dist/objectstack.json`) are picked up by
|
|
2119
|
+
// the running server WITHOUT requiring a manual restart.
|
|
2120
|
+
// Uses polling under the hood (see plugin.ts) to avoid
|
|
2121
|
+
// `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2122
|
+
artifactWatch: process.env.NODE_ENV !== "production",
|
|
1917
2123
|
environmentId,
|
|
1918
|
-
|
|
2124
|
+
artifactSource: { mode: "local-file", path: artifactPath }
|
|
2125
|
+
}),
|
|
2126
|
+
new ObjectQLPlugin({ environmentId })
|
|
2127
|
+
];
|
|
2128
|
+
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
2129
|
+
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
2130
|
+
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
2131
|
+
const manifest = artifactBundle?.manifest;
|
|
2132
|
+
return {
|
|
2133
|
+
plugins,
|
|
2134
|
+
api: {
|
|
2135
|
+
enableProjectScoping: false,
|
|
2136
|
+
projectResolution: "none"
|
|
2137
|
+
},
|
|
2138
|
+
...requires ? { requires } : {},
|
|
2139
|
+
...objects ? { objects } : {},
|
|
2140
|
+
...manifest ? { manifest } : {}
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
var import_node_path3, import_node_fs2, import_node_os, import_zod, import_types2, StandaloneStackConfigSchema;
|
|
2144
|
+
var init_standalone_stack = __esm({
|
|
2145
|
+
"src/standalone-stack.ts"() {
|
|
2146
|
+
"use strict";
|
|
2147
|
+
import_node_path3 = require("path");
|
|
2148
|
+
import_node_fs2 = require("fs");
|
|
2149
|
+
import_node_os = require("os");
|
|
2150
|
+
import_zod = require("zod");
|
|
2151
|
+
import_types2 = require("@objectstack/types");
|
|
2152
|
+
init_load_artifact_bundle();
|
|
2153
|
+
StandaloneStackConfigSchema = import_zod.z.object({
|
|
2154
|
+
databaseUrl: import_zod.z.string().optional(),
|
|
2155
|
+
databaseAuthToken: import_zod.z.string().optional(),
|
|
2156
|
+
databaseDriver: import_zod.z.enum(["sqlite", "sqlite-wasm", "memory", "postgres", "mongodb"]).optional(),
|
|
2157
|
+
environmentId: import_zod.z.string().optional(),
|
|
2158
|
+
artifactPath: import_zod.z.string().optional(),
|
|
2159
|
+
/**
|
|
2160
|
+
* Project root directory. When set (typically by the CLI after locating
|
|
2161
|
+
* `objectstack.config.ts`), the default sqlite database is placed under
|
|
2162
|
+
* `<projectRoot>/.objectstack/data/standalone.db` instead of the global
|
|
2163
|
+
* `~/.objectstack/data/standalone.db`. This keeps per-project data
|
|
2164
|
+
* scoped to the project folder so different examples / apps don't
|
|
2165
|
+
* share a single database by accident.
|
|
2166
|
+
*
|
|
2167
|
+
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
2168
|
+
* precedence over this default.
|
|
2169
|
+
*/
|
|
2170
|
+
projectRoot: import_zod.z.string().optional()
|
|
1919
2171
|
});
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1923
|
-
if (!existing) {
|
|
1924
|
-
const redirects = desiredRedirect ? [desiredRedirect] : [];
|
|
1925
|
-
try {
|
|
1926
|
-
await ql.insert("sys_oauth_application", {
|
|
1927
|
-
id: `oauthc_${environmentId}`,
|
|
1928
|
-
name: `Project ${environmentId}`,
|
|
1929
|
-
client_id: clientId,
|
|
1930
|
-
client_secret: clientSecretStored,
|
|
1931
|
-
type: "web",
|
|
1932
|
-
redirect_uris: JSON.stringify(redirects),
|
|
1933
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
1934
|
-
response_types: JSON.stringify(["code"]),
|
|
1935
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
1936
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
1937
|
-
require_pkce: false,
|
|
1938
|
-
skip_consent: true,
|
|
1939
|
-
disabled: false,
|
|
1940
|
-
subject_type: "public",
|
|
1941
|
-
created_at: nowIso,
|
|
1942
|
-
updated_at: nowIso
|
|
1943
|
-
}, { context: { isSystem: true } });
|
|
1944
|
-
logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
|
|
1945
|
-
} catch (err) {
|
|
1946
|
-
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
1947
|
-
environmentId,
|
|
1948
|
-
error: err?.message
|
|
1949
|
-
});
|
|
1950
|
-
if (throwOnError) throw err;
|
|
1951
|
-
}
|
|
1952
|
-
return;
|
|
1953
|
-
}
|
|
1954
|
-
let currentRedirects = [];
|
|
1955
|
-
try {
|
|
1956
|
-
const raw = existing.redirect_uris;
|
|
1957
|
-
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
1958
|
-
if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
|
|
1959
|
-
} catch {
|
|
1960
|
-
}
|
|
1961
|
-
const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
|
|
1962
|
-
const repairPatch = {
|
|
1963
|
-
name: existing.name || `Project ${environmentId}`,
|
|
1964
|
-
client_secret: clientSecretStored,
|
|
1965
|
-
type: existing.type || "web",
|
|
1966
|
-
redirect_uris: JSON.stringify(mergedRedirects),
|
|
1967
|
-
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
1968
|
-
response_types: JSON.stringify(["code"]),
|
|
1969
|
-
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
1970
|
-
token_endpoint_auth_method: "client_secret_basic",
|
|
1971
|
-
require_pkce: false,
|
|
1972
|
-
skip_consent: true,
|
|
1973
|
-
disabled: false,
|
|
1974
|
-
subject_type: "public",
|
|
1975
|
-
updated_at: nowIso
|
|
1976
|
-
};
|
|
1977
|
-
try {
|
|
1978
|
-
await ql.update(
|
|
1979
|
-
"sys_oauth_application",
|
|
1980
|
-
repairPatch,
|
|
1981
|
-
{ where: { id: existing.id } },
|
|
1982
|
-
{ context: { isSystem: true } }
|
|
1983
|
-
);
|
|
1984
|
-
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
1985
|
-
environmentId,
|
|
1986
|
-
clientId,
|
|
1987
|
-
redirect_uris: mergedRedirects
|
|
1988
|
-
});
|
|
1989
|
-
} catch (err) {
|
|
1990
|
-
logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
|
|
1991
|
-
environmentId,
|
|
1992
|
-
error: err?.message
|
|
1993
|
-
});
|
|
1994
|
-
if (throwOnError) throw err;
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
async function backfillPlatformSsoClients(opts) {
|
|
1998
|
-
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
1999
|
-
if (!baseSecret) {
|
|
2000
|
-
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
2001
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
2002
|
-
}
|
|
2003
|
-
let projects = [];
|
|
2004
|
-
try {
|
|
2005
|
-
const rows = await ql.find("sys_environment", {
|
|
2006
|
-
limit,
|
|
2007
|
-
fields: ["id", "hostname", "status"]
|
|
2008
|
-
}, { context: { isSystem: true } });
|
|
2009
|
-
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
2010
|
-
} catch (err) {
|
|
2011
|
-
logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
|
|
2012
|
-
error: err?.message
|
|
2013
|
-
});
|
|
2014
|
-
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
|
|
2015
|
-
}
|
|
2016
|
-
let seeded = 0;
|
|
2017
|
-
let alreadyExisted = 0;
|
|
2018
|
-
const failures = [];
|
|
2019
|
-
for (const p of projects) {
|
|
2020
|
-
if (!p?.id) continue;
|
|
2021
|
-
const before = await (async () => {
|
|
2022
|
-
try {
|
|
2023
|
-
const r = await ql.find("sys_oauth_application", {
|
|
2024
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
2025
|
-
limit: 1
|
|
2026
|
-
}, { context: { isSystem: true } });
|
|
2027
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
2028
|
-
return list[0] ?? null;
|
|
2029
|
-
} catch {
|
|
2030
|
-
return null;
|
|
2031
|
-
}
|
|
2032
|
-
})();
|
|
2033
|
-
try {
|
|
2034
|
-
await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
2035
|
-
if (before) alreadyExisted++;
|
|
2036
|
-
else {
|
|
2037
|
-
const after = await (async () => {
|
|
2038
|
-
try {
|
|
2039
|
-
const r = await ql.find("sys_oauth_application", {
|
|
2040
|
-
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
2041
|
-
limit: 1
|
|
2042
|
-
}, { context: { isSystem: true } });
|
|
2043
|
-
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
2044
|
-
return list[0] ?? null;
|
|
2045
|
-
} catch (err) {
|
|
2046
|
-
return { _readErr: err?.message };
|
|
2047
|
-
}
|
|
2048
|
-
})();
|
|
2049
|
-
if (after && !after._readErr) seeded++;
|
|
2050
|
-
else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
2051
|
-
}
|
|
2052
|
-
} catch (err) {
|
|
2053
|
-
failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
2057
|
-
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
2058
|
-
}
|
|
2059
|
-
var import_node_crypto, PLATFORM_SSO_PROVIDER_ID;
|
|
2060
|
-
var init_platform_sso = __esm({
|
|
2061
|
-
"src/cloud/platform-sso.ts"() {
|
|
2062
|
-
"use strict";
|
|
2063
|
-
import_node_crypto = require("crypto");
|
|
2064
|
-
PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
2065
2172
|
}
|
|
2066
2173
|
});
|
|
2067
2174
|
|
|
@@ -2222,6 +2329,7 @@ __export(index_exports, {
|
|
|
2222
2329
|
DEFAULT_CLOUD_URL: () => DEFAULT_CLOUD_URL,
|
|
2223
2330
|
DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
|
|
2224
2331
|
DriverPlugin: () => DriverPlugin,
|
|
2332
|
+
ExternalValidationPlugin: () => ExternalValidationPlugin,
|
|
2225
2333
|
FileArtifactApiClient: () => FileArtifactApiClient,
|
|
2226
2334
|
HttpDispatcher: () => HttpDispatcher,
|
|
2227
2335
|
HttpServer: () => HttpServer,
|
|
@@ -2250,7 +2358,7 @@ __export(index_exports, {
|
|
|
2250
2358
|
SandboxError: () => SandboxError,
|
|
2251
2359
|
SeedLoaderService: () => SeedLoaderService,
|
|
2252
2360
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
2253
|
-
_resetEnvDeprecationWarnings: () =>
|
|
2361
|
+
_resetEnvDeprecationWarnings: () => import_types5._resetEnvDeprecationWarnings,
|
|
2254
2362
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
2255
2363
|
backfillPlatformSsoClients: () => backfillPlatformSsoClients,
|
|
2256
2364
|
buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
|
|
@@ -2260,6 +2368,7 @@ __export(index_exports, {
|
|
|
2260
2368
|
collectBundleHooks: () => collectBundleHooks,
|
|
2261
2369
|
createDefaultHostConfig: () => createDefaultHostConfig,
|
|
2262
2370
|
createDispatcherPlugin: () => createDispatcherPlugin,
|
|
2371
|
+
createExternalValidationPlugin: () => createExternalValidationPlugin,
|
|
2263
2372
|
createObjectOSStack: () => createObjectOSStack,
|
|
2264
2373
|
createRestApiPlugin: () => import_rest.createRestApiPlugin,
|
|
2265
2374
|
createStandaloneStack: () => createStandaloneStack,
|
|
@@ -2275,7 +2384,7 @@ __export(index_exports, {
|
|
|
2275
2384
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
2276
2385
|
parseTraceparent: () => parseTraceparent,
|
|
2277
2386
|
readArtifactSource: () => readArtifactSource,
|
|
2278
|
-
readEnvWithDeprecation: () =>
|
|
2387
|
+
readEnvWithDeprecation: () => import_types5.readEnvWithDeprecation,
|
|
2279
2388
|
resolveCloudUrl: () => resolveCloudUrl,
|
|
2280
2389
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
2281
2390
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
@@ -2333,228 +2442,234 @@ var Runtime = class {
|
|
|
2333
2442
|
}
|
|
2334
2443
|
};
|
|
2335
2444
|
|
|
2336
|
-
// src/
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
var
|
|
2341
|
-
var
|
|
2445
|
+
// src/index.ts
|
|
2446
|
+
init_standalone_stack();
|
|
2447
|
+
|
|
2448
|
+
// src/default-host.ts
|
|
2449
|
+
var import_node_path4 = require("path");
|
|
2450
|
+
var import_node_fs3 = require("fs");
|
|
2451
|
+
init_standalone_stack();
|
|
2342
2452
|
init_load_artifact_bundle();
|
|
2343
|
-
function
|
|
2344
|
-
const
|
|
2345
|
-
if (
|
|
2346
|
-
|
|
2347
|
-
|
|
2453
|
+
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
2454
|
+
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path4.resolve)(cwd, "dist/objectstack.json");
|
|
2455
|
+
if (isHttpUrl(candidate)) return candidate;
|
|
2456
|
+
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
|
|
2457
|
+
return (0, import_node_fs3.existsSync)(candidate) ? candidate : void 0;
|
|
2458
|
+
}
|
|
2459
|
+
async function createDefaultHostConfig(options = {}) {
|
|
2460
|
+
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
2461
|
+
let resolvedArtifact = resolveDefaultArtifactPath(standaloneOpts.artifactPath);
|
|
2462
|
+
if (!resolvedArtifact && requireArtifact) {
|
|
2463
|
+
throw new Error(
|
|
2464
|
+
"[createDefaultHostConfig] No artifact source available. Set OS_ARTIFACT_PATH (file path or http(s):// URL), place the artifact at <cwd>/dist/objectstack.json, or pass `{ artifactPath: ... }` explicitly. To boot an empty kernel anyway, pass `{ requireArtifact: false }`."
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
if (!resolvedArtifact && !requireArtifact) {
|
|
2468
|
+
const home = resolveObjectStackHome();
|
|
2469
|
+
const stubPath = (0, import_node_path4.resolve)(home, "dist/objectstack.json");
|
|
2470
|
+
if (!(0, import_node_fs3.existsSync)(stubPath)) {
|
|
2471
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path4.resolve)(stubPath, ".."), { recursive: true });
|
|
2472
|
+
(0, import_node_fs3.writeFileSync)(
|
|
2473
|
+
stubPath,
|
|
2474
|
+
JSON.stringify(
|
|
2475
|
+
{
|
|
2476
|
+
manifest: {
|
|
2477
|
+
id: "com.objectstack.empty",
|
|
2478
|
+
name: "empty",
|
|
2479
|
+
version: "0.0.0",
|
|
2480
|
+
type: "app",
|
|
2481
|
+
description: "Empty starter kernel \u2014 install apps via the Studio marketplace."
|
|
2482
|
+
},
|
|
2483
|
+
objects: [],
|
|
2484
|
+
views: [],
|
|
2485
|
+
apps: [],
|
|
2486
|
+
flows: [],
|
|
2487
|
+
requires: []
|
|
2488
|
+
},
|
|
2489
|
+
null,
|
|
2490
|
+
2
|
|
2491
|
+
),
|
|
2492
|
+
"utf8"
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
resolvedArtifact = stubPath;
|
|
2348
2496
|
}
|
|
2349
|
-
return (
|
|
2497
|
+
return createStandaloneStack({
|
|
2498
|
+
...standaloneOpts,
|
|
2499
|
+
artifactPath: resolvedArtifact
|
|
2500
|
+
});
|
|
2350
2501
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2502
|
+
|
|
2503
|
+
// src/index.ts
|
|
2504
|
+
init_driver_plugin();
|
|
2505
|
+
init_app_plugin();
|
|
2506
|
+
init_seed_loader();
|
|
2507
|
+
|
|
2508
|
+
// src/external-validation-plugin.ts
|
|
2509
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
2510
|
+
var ExternalValidationPlugin = class {
|
|
2511
|
+
constructor() {
|
|
2512
|
+
this.name = "com.objectstack.external-validation";
|
|
2513
|
+
this.type = "standard";
|
|
2514
|
+
this.version = "1.0.0";
|
|
2515
|
+
/** Active background drift-check timers, keyed by datasource name. */
|
|
2516
|
+
this.driftTimers = /* @__PURE__ */ new Map();
|
|
2517
|
+
this.init = (_ctx) => {
|
|
2518
|
+
};
|
|
2519
|
+
this.start = (ctx) => {
|
|
2520
|
+
ctx.hook("kernel:ready", async () => {
|
|
2521
|
+
await this.runValidation(ctx);
|
|
2522
|
+
await this.scheduleDriftChecks(ctx);
|
|
2523
|
+
});
|
|
2524
|
+
};
|
|
2525
|
+
/** Tear down background drift-check timers (idempotent). */
|
|
2526
|
+
this.stop = () => {
|
|
2527
|
+
for (const timer of this.driftTimers.values()) clearInterval(timer);
|
|
2528
|
+
this.driftTimers.clear();
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
/** Exposed for testing; invoked from the kernel:ready handler. */
|
|
2532
|
+
async runValidation(ctx) {
|
|
2533
|
+
const svc = safeGet(ctx, "external-datasource");
|
|
2534
|
+
if (!svc?.validateAll) {
|
|
2535
|
+
ctx.logger?.debug?.("[external-validation] service not registered; skipping");
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const metadata = safeGet(ctx, "metadata");
|
|
2539
|
+
let report;
|
|
2540
|
+
try {
|
|
2541
|
+
report = await svc.validateAll();
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
ctx.logger?.warn?.("[external-validation] validateAll failed", { err });
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
const failures = report.results.filter((r) => !r.ok);
|
|
2547
|
+
if (failures.length === 0) {
|
|
2548
|
+
ctx.logger?.info?.("[external-validation] all federated objects match their remote schema", {
|
|
2549
|
+
objects: report.results.length
|
|
2550
|
+
});
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
for (const r of failures) {
|
|
2554
|
+
const mode = await resolveOnMismatch(metadata, r.datasource);
|
|
2555
|
+
if (mode === "ignore") continue;
|
|
2556
|
+
if (mode === "warn") {
|
|
2557
|
+
ctx.logger?.warn?.("[external-validation] external schema drift", {
|
|
2558
|
+
datasource: r.datasource,
|
|
2559
|
+
object: r.object,
|
|
2560
|
+
diffs: r.diffs
|
|
2561
|
+
});
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
throw new import_shared.ExternalSchemaMismatchError(r.datasource, r.object, r.diffs);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2357
2567
|
/**
|
|
2358
|
-
*
|
|
2359
|
-
* `
|
|
2360
|
-
*
|
|
2361
|
-
*
|
|
2362
|
-
* scoped to the project folder so different examples / apps don't
|
|
2363
|
-
* share a single database by accident.
|
|
2568
|
+
* Arm a background drift checker for every federated datasource that declares
|
|
2569
|
+
* `external.validation.checkIntervalMs`. Each fires on its own interval and
|
|
2570
|
+
* emits `external.schema.drift` events — it never throws or aborts the
|
|
2571
|
+
* process, since drift past boot is observational, not fatal.
|
|
2364
2572
|
*
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2573
|
+
* No-op when metadata can't be enumerated or no datasource opts in. Re-arming
|
|
2574
|
+
* (e.g. a second `kernel:ready`) first clears existing timers so intervals
|
|
2575
|
+
* don't accumulate.
|
|
2367
2576
|
*/
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
2374
|
-
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
2375
|
-
if (/\.wasm\.db$/i.test(dbUrl)) return "sqlite-wasm";
|
|
2376
|
-
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
2377
|
-
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
2378
|
-
throw new Error(
|
|
2379
|
-
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
2380
|
-
);
|
|
2381
|
-
}
|
|
2382
|
-
async function createStandaloneStack(config) {
|
|
2383
|
-
const cfg = StandaloneStackConfigSchema.parse(config ?? {});
|
|
2384
|
-
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
2385
|
-
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
2386
|
-
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
2387
|
-
const { AppPlugin: AppPlugin2 } = await Promise.resolve().then(() => (init_app_plugin(), app_plugin_exports));
|
|
2388
|
-
const cwd = process.cwd();
|
|
2389
|
-
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2390
|
-
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path2.resolve)(cwd, "dist/objectstack.json");
|
|
2391
|
-
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : (0, import_node_path2.resolve)(cwd, artifactPathInput);
|
|
2392
|
-
const dbUrl = cfg.databaseUrl ?? (0, import_types2.readEnvWithDeprecation)("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${(0, import_node_path2.resolve)(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${(0, import_node_path2.resolve)(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${(0, import_node_path2.resolve)(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
2393
|
-
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2394
|
-
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
2395
|
-
let driverPlugin;
|
|
2396
|
-
if (dbDriver === "memory") {
|
|
2397
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2398
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2399
|
-
} else if (dbDriver === "postgres") {
|
|
2400
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2401
|
-
driverPlugin = new DriverPlugin2(
|
|
2402
|
-
new SqlDriver({
|
|
2403
|
-
client: "pg",
|
|
2404
|
-
connection: dbUrl,
|
|
2405
|
-
pool: { min: 0, max: 5 }
|
|
2406
|
-
})
|
|
2407
|
-
);
|
|
2408
|
-
} else if (dbDriver === "mongodb") {
|
|
2409
|
-
let MongoDBDriver;
|
|
2577
|
+
async scheduleDriftChecks(ctx) {
|
|
2578
|
+
this.stop();
|
|
2579
|
+
const metadata = safeGet(ctx, "metadata");
|
|
2580
|
+
if (!metadata?.list) return;
|
|
2581
|
+
let datasources;
|
|
2410
2582
|
try {
|
|
2411
|
-
|
|
2583
|
+
datasources = await metadata.list("datasource");
|
|
2412
2584
|
} catch (err) {
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
);
|
|
2416
|
-
}
|
|
2417
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
2418
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
2419
|
-
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
2420
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
2421
|
-
if (filename && filename !== ":memory:") {
|
|
2422
|
-
(0, import_node_fs.mkdirSync)((0, import_node_path2.resolve)(filename, ".."), { recursive: true });
|
|
2585
|
+
ctx.logger?.warn?.("[external-validation] could not list datasources for drift checks", { err });
|
|
2586
|
+
return;
|
|
2423
2587
|
}
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
);
|
|
2588
|
+
for (const def of datasources) {
|
|
2589
|
+
const interval = def?.external?.validation?.checkIntervalMs;
|
|
2590
|
+
const name = def?.name;
|
|
2591
|
+
if (!name || typeof interval !== "number" || interval <= 0) continue;
|
|
2592
|
+
const timer = setInterval(() => {
|
|
2593
|
+
void this.runDriftCheck(ctx, name);
|
|
2594
|
+
}, interval);
|
|
2595
|
+
timer.unref?.();
|
|
2596
|
+
this.driftTimers.set(name, timer);
|
|
2597
|
+
ctx.logger?.info?.("[external-validation] armed background drift check", {
|
|
2598
|
+
datasource: name,
|
|
2599
|
+
intervalMs: interval
|
|
2600
|
+
});
|
|
2437
2601
|
}
|
|
2438
|
-
(0, import_node_fs.mkdirSync)((0, import_node_path2.resolve)(filename, ".."), { recursive: true });
|
|
2439
|
-
driverPlugin = new DriverPlugin2(
|
|
2440
|
-
new SqlDriver({
|
|
2441
|
-
client: "better-sqlite3",
|
|
2442
|
-
connection: { filename },
|
|
2443
|
-
useNullAsDefault: true
|
|
2444
|
-
})
|
|
2445
|
-
);
|
|
2446
2602
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
);
|
|
2603
|
+
/**
|
|
2604
|
+
* Re-validate one datasource's federated objects and emit an
|
|
2605
|
+
* `external.schema.drift` event per mismatch. Exposed for testing; invoked
|
|
2606
|
+
* from the interval armed by {@link scheduleDriftChecks}. Never throws.
|
|
2607
|
+
*
|
|
2608
|
+
* @returns the number of drift events emitted.
|
|
2609
|
+
*/
|
|
2610
|
+
async runDriftCheck(ctx, datasource) {
|
|
2611
|
+
const svc = safeGet(ctx, "external-datasource");
|
|
2612
|
+
if (!svc?.validateAll) return 0;
|
|
2613
|
+
let report;
|
|
2614
|
+
try {
|
|
2615
|
+
report = await svc.validateAll();
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
ctx.logger?.warn?.("[external-validation] drift check validateAll failed", {
|
|
2618
|
+
datasource,
|
|
2619
|
+
err
|
|
2620
|
+
});
|
|
2621
|
+
return 0;
|
|
2622
|
+
}
|
|
2623
|
+
const drifted = report.results.filter((r) => !r.ok && r.datasource === datasource);
|
|
2624
|
+
for (const r of drifted) {
|
|
2625
|
+
const event = {
|
|
2626
|
+
datasource: r.datasource,
|
|
2627
|
+
object: r.object,
|
|
2628
|
+
diffs: r.diffs
|
|
2629
|
+
};
|
|
2630
|
+
try {
|
|
2631
|
+
await ctx.trigger("external.schema.drift", event);
|
|
2632
|
+
} catch (err) {
|
|
2633
|
+
ctx.logger?.warn?.("[external-validation] failed to emit drift event", {
|
|
2634
|
+
datasource,
|
|
2635
|
+
object: r.object,
|
|
2636
|
+
err
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
if (drifted.length > 0) {
|
|
2641
|
+
ctx.logger?.warn?.("[external-validation] background drift detected", {
|
|
2642
|
+
datasource,
|
|
2643
|
+
objects: drifted.map((r) => r.object)
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
return drifted.length;
|
|
2456
2647
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
// Source-file scanner OFF — declarative metadata is loaded
|
|
2461
|
-
// from the compiled artifact, not from yaml/json files on
|
|
2462
|
-
// disk. Scanning would also recursively watch the project
|
|
2463
|
-
// root (incl. node_modules), which is expensive and prone
|
|
2464
|
-
// to EMFILE.
|
|
2465
|
-
watch: false,
|
|
2466
|
-
// Artifact-file HMR ON in non-production so edits to
|
|
2467
|
-
// `*.view.ts` / `*.flow.ts` (which the CLI dev-mode watcher
|
|
2468
|
-
// recompiles into `dist/objectstack.json`) are picked up by
|
|
2469
|
-
// the running server WITHOUT requiring a manual restart.
|
|
2470
|
-
// Uses polling under the hood (see plugin.ts) to avoid
|
|
2471
|
-
// `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2472
|
-
artifactWatch: process.env.NODE_ENV !== "production",
|
|
2473
|
-
environmentId,
|
|
2474
|
-
artifactSource: { mode: "local-file", path: artifactPath }
|
|
2475
|
-
}),
|
|
2476
|
-
new ObjectQLPlugin({ environmentId })
|
|
2477
|
-
];
|
|
2478
|
-
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
2479
|
-
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
2480
|
-
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
2481
|
-
const manifest = artifactBundle?.manifest;
|
|
2482
|
-
return {
|
|
2483
|
-
plugins,
|
|
2484
|
-
api: {
|
|
2485
|
-
enableProjectScoping: false,
|
|
2486
|
-
projectResolution: "none"
|
|
2487
|
-
},
|
|
2488
|
-
...requires ? { requires } : {},
|
|
2489
|
-
...objects ? { objects } : {},
|
|
2490
|
-
...manifest ? { manifest } : {}
|
|
2491
|
-
};
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
// src/default-host.ts
|
|
2495
|
-
var import_node_path3 = require("path");
|
|
2496
|
-
var import_node_fs2 = require("fs");
|
|
2497
|
-
init_load_artifact_bundle();
|
|
2498
|
-
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
2499
|
-
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path3.resolve)(cwd, "dist/objectstack.json");
|
|
2500
|
-
if (isHttpUrl(candidate)) return candidate;
|
|
2501
|
-
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
|
|
2502
|
-
return (0, import_node_fs2.existsSync)(candidate) ? candidate : void 0;
|
|
2648
|
+
};
|
|
2649
|
+
function createExternalValidationPlugin() {
|
|
2650
|
+
return new ExternalValidationPlugin();
|
|
2503
2651
|
}
|
|
2504
|
-
async function
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
);
|
|
2652
|
+
async function resolveOnMismatch(metadata, datasource) {
|
|
2653
|
+
try {
|
|
2654
|
+
const ds = await metadata?.get?.("datasource", datasource);
|
|
2655
|
+
return ds?.external?.validation?.onMismatch ?? "fail";
|
|
2656
|
+
} catch {
|
|
2657
|
+
return "fail";
|
|
2511
2658
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
stubPath,
|
|
2519
|
-
JSON.stringify(
|
|
2520
|
-
{
|
|
2521
|
-
manifest: {
|
|
2522
|
-
id: "com.objectstack.empty",
|
|
2523
|
-
name: "empty",
|
|
2524
|
-
version: "0.0.0",
|
|
2525
|
-
type: "app",
|
|
2526
|
-
description: "Empty starter kernel \u2014 install apps via the Studio marketplace."
|
|
2527
|
-
},
|
|
2528
|
-
objects: [],
|
|
2529
|
-
views: [],
|
|
2530
|
-
apps: [],
|
|
2531
|
-
flows: [],
|
|
2532
|
-
requires: []
|
|
2533
|
-
},
|
|
2534
|
-
null,
|
|
2535
|
-
2
|
|
2536
|
-
),
|
|
2537
|
-
"utf8"
|
|
2538
|
-
);
|
|
2539
|
-
}
|
|
2540
|
-
resolvedArtifact = stubPath;
|
|
2659
|
+
}
|
|
2660
|
+
function safeGet(ctx, name) {
|
|
2661
|
+
try {
|
|
2662
|
+
return ctx.getService(name);
|
|
2663
|
+
} catch {
|
|
2664
|
+
return void 0;
|
|
2541
2665
|
}
|
|
2542
|
-
return createStandaloneStack({
|
|
2543
|
-
...standaloneOpts,
|
|
2544
|
-
artifactPath: resolvedArtifact
|
|
2545
|
-
});
|
|
2546
2666
|
}
|
|
2547
2667
|
|
|
2548
|
-
// src/index.ts
|
|
2549
|
-
init_driver_plugin();
|
|
2550
|
-
init_app_plugin();
|
|
2551
|
-
init_seed_loader();
|
|
2552
|
-
|
|
2553
2668
|
// src/http-dispatcher.ts
|
|
2554
2669
|
var import_core2 = require("@objectstack/core");
|
|
2555
|
-
var
|
|
2556
|
-
var
|
|
2557
|
-
|
|
2670
|
+
var import_system2 = require("@objectstack/spec/system");
|
|
2671
|
+
var import_shared2 = require("@objectstack/spec/shared");
|
|
2672
|
+
init_package_state_store();
|
|
2558
2673
|
|
|
2559
2674
|
// src/security/resolve-execution-context.ts
|
|
2560
2675
|
function readHeader(headers, name) {
|
|
@@ -3016,7 +3131,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3016
3131
|
}
|
|
3017
3132
|
}
|
|
3018
3133
|
try {
|
|
3019
|
-
const authService = await this.getService(
|
|
3134
|
+
const authService = await this.getService(import_system2.CoreServiceName.enum.auth);
|
|
3020
3135
|
const sessionData = await authService?.api?.getSession?.({
|
|
3021
3136
|
headers: context.request?.headers
|
|
3022
3137
|
});
|
|
@@ -3097,7 +3212,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3097
3212
|
let userId;
|
|
3098
3213
|
let activeOrganizationId;
|
|
3099
3214
|
try {
|
|
3100
|
-
const authService = await this.resolveService(
|
|
3215
|
+
const authService = await this.resolveService(import_system2.CoreServiceName.enum.auth);
|
|
3101
3216
|
const sessionData = await authService?.api?.getSession?.({
|
|
3102
3217
|
headers: context.request?.headers
|
|
3103
3218
|
});
|
|
@@ -3166,21 +3281,21 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3166
3281
|
queueSvc,
|
|
3167
3282
|
jobSvc
|
|
3168
3283
|
] = await Promise.all([
|
|
3169
|
-
this.resolveService(
|
|
3170
|
-
this.resolveService(
|
|
3171
|
-
this.resolveService(
|
|
3172
|
-
this.resolveService(
|
|
3173
|
-
this.resolveService(
|
|
3174
|
-
this.resolveService(
|
|
3175
|
-
this.resolveService(
|
|
3176
|
-
this.resolveService(
|
|
3177
|
-
this.resolveService(
|
|
3178
|
-
this.resolveService(
|
|
3179
|
-
this.resolveService(
|
|
3180
|
-
this.resolveService(
|
|
3181
|
-
this.resolveService(
|
|
3182
|
-
this.resolveService(
|
|
3183
|
-
this.resolveService(
|
|
3284
|
+
this.resolveService(import_system2.CoreServiceName.enum.auth),
|
|
3285
|
+
this.resolveService(import_system2.CoreServiceName.enum.graphql),
|
|
3286
|
+
this.resolveService(import_system2.CoreServiceName.enum.search),
|
|
3287
|
+
this.resolveService(import_system2.CoreServiceName.enum.realtime),
|
|
3288
|
+
this.resolveService(import_system2.CoreServiceName.enum["file-storage"]),
|
|
3289
|
+
this.resolveService(import_system2.CoreServiceName.enum.analytics),
|
|
3290
|
+
this.resolveService(import_system2.CoreServiceName.enum.workflow),
|
|
3291
|
+
this.resolveService(import_system2.CoreServiceName.enum.ai),
|
|
3292
|
+
this.resolveService(import_system2.CoreServiceName.enum.notification),
|
|
3293
|
+
this.resolveService(import_system2.CoreServiceName.enum.i18n),
|
|
3294
|
+
this.resolveService(import_system2.CoreServiceName.enum.ui),
|
|
3295
|
+
this.resolveService(import_system2.CoreServiceName.enum.automation),
|
|
3296
|
+
this.resolveService(import_system2.CoreServiceName.enum.cache),
|
|
3297
|
+
this.resolveService(import_system2.CoreServiceName.enum.queue),
|
|
3298
|
+
this.resolveService(import_system2.CoreServiceName.enum.job)
|
|
3184
3299
|
]);
|
|
3185
3300
|
const hasAuth = !!authSvc;
|
|
3186
3301
|
const hasGraphQL = !!(graphqlSvc || this.kernel.graphql);
|
|
@@ -3297,7 +3412,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3297
3412
|
* path: sub-path after /auth/
|
|
3298
3413
|
*/
|
|
3299
3414
|
async handleAuth(path, method, body, context) {
|
|
3300
|
-
const authService = await this.getService(
|
|
3415
|
+
const authService = await this.getService(import_system2.CoreServiceName.enum.auth);
|
|
3301
3416
|
if (authService && typeof authService.handler === "function") {
|
|
3302
3417
|
const response = await authService.handler(context.request, context.response);
|
|
3303
3418
|
return { handled: true, result: response };
|
|
@@ -3381,10 +3496,21 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3381
3496
|
}
|
|
3382
3497
|
return { handled: true, response: this.success({ types: ["object", "app", "plugin"] }) };
|
|
3383
3498
|
}
|
|
3499
|
+
if (parts.length === 4 && (parts[0] === "objects" || parts[0] === "object") && parts[2] === "state" && (!method || method === "GET")) {
|
|
3500
|
+
const name = parts[1];
|
|
3501
|
+
const field = parts[3];
|
|
3502
|
+
const from = query?.from !== void 0 ? String(query.from) : void 0;
|
|
3503
|
+
const qlService = await this.getObjectQLService();
|
|
3504
|
+
const schema = qlService?.registry?.getObject(name);
|
|
3505
|
+
if (!schema) return { handled: true, response: this.error("Object not found", 404) };
|
|
3506
|
+
const { legalNextStates } = await import("@objectstack/objectql");
|
|
3507
|
+
const next = from === void 0 ? null : legalNextStates(schema, field, from);
|
|
3508
|
+
return { handled: true, response: this.success({ object: name, field, from: from ?? null, next }) };
|
|
3509
|
+
}
|
|
3384
3510
|
if (parts.length >= 3 && parts[parts.length - 1] === "published" && (!method || method === "GET")) {
|
|
3385
3511
|
const type = parts[0];
|
|
3386
3512
|
const name = parts.slice(1, -1).join("/");
|
|
3387
|
-
const metadataService = await this.getService(
|
|
3513
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3388
3514
|
if (metadataService && typeof metadataService.getPublished === "function") {
|
|
3389
3515
|
const data = await metadataService.getPublished(type, name);
|
|
3390
3516
|
if (data === void 0) return { handled: true, response: this.error("Not found", 404) };
|
|
@@ -3409,7 +3535,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3409
3535
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
3410
3536
|
try {
|
|
3411
3537
|
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
3412
|
-
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId });
|
|
3538
|
+
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId, ...packageId ? { packageId } : {} });
|
|
3413
3539
|
return { handled: true, response: this.success(result) };
|
|
3414
3540
|
} catch (e) {
|
|
3415
3541
|
return { handled: true, response: this.error(e.message, 400) };
|
|
@@ -3458,7 +3584,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3458
3584
|
}
|
|
3459
3585
|
return { handled: true, response: this.error("Not found", 404) };
|
|
3460
3586
|
}
|
|
3461
|
-
const singularType = (0,
|
|
3587
|
+
const singularType = (0, import_shared2.pluralToSingular)(type);
|
|
3462
3588
|
const protocol = await this.resolveService("protocol");
|
|
3463
3589
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
3464
3590
|
try {
|
|
@@ -3495,7 +3621,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3495
3621
|
} catch {
|
|
3496
3622
|
}
|
|
3497
3623
|
}
|
|
3498
|
-
const metadataService = await this.getService(
|
|
3624
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3499
3625
|
if (metadataService && typeof metadataService.list === "function") {
|
|
3500
3626
|
try {
|
|
3501
3627
|
let items = await metadataService.list(typeOrName);
|
|
@@ -3629,7 +3755,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3629
3755
|
* path: sub-path after /analytics/
|
|
3630
3756
|
*/
|
|
3631
3757
|
async handleAnalytics(path, method, body, _context) {
|
|
3632
|
-
const analyticsService = await this.getService(
|
|
3758
|
+
const analyticsService = await this.getService(import_system2.CoreServiceName.enum.analytics);
|
|
3633
3759
|
if (!analyticsService) return { handled: false };
|
|
3634
3760
|
const m = method.toUpperCase();
|
|
3635
3761
|
const subPath = path.replace(/^\/+/, "");
|
|
@@ -3659,7 +3785,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3659
3785
|
* GET /labels/:object?locale=xx → getFieldLabels (locale from query)
|
|
3660
3786
|
*/
|
|
3661
3787
|
async handleI18n(path, method, query, _context) {
|
|
3662
|
-
const i18nService = await this.getService(
|
|
3788
|
+
const i18nService = await this.getService(import_system2.CoreServiceName.enum.i18n);
|
|
3663
3789
|
if (!i18nService) return { handled: true, response: this.error("i18n service not available", 501) };
|
|
3664
3790
|
const m = method.toUpperCase();
|
|
3665
3791
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -3749,17 +3875,27 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3749
3875
|
const id = decodeURIComponent(parts[0]);
|
|
3750
3876
|
const pkg = registry.enablePackage(id);
|
|
3751
3877
|
if (!pkg) return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3878
|
+
try {
|
|
3879
|
+
setPackageDisabled(_context?.environmentId, id, false);
|
|
3880
|
+
} catch (err) {
|
|
3881
|
+
console.warn("[handlePackages] failed to persist enable state", { id, error: err?.message });
|
|
3882
|
+
}
|
|
3752
3883
|
return { handled: true, response: this.success(pkg) };
|
|
3753
3884
|
}
|
|
3754
3885
|
if (parts.length === 2 && parts[1] === "disable" && m === "PATCH") {
|
|
3755
3886
|
const id = decodeURIComponent(parts[0]);
|
|
3756
3887
|
const pkg = registry.disablePackage(id);
|
|
3757
3888
|
if (!pkg) return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3889
|
+
try {
|
|
3890
|
+
setPackageDisabled(_context?.environmentId, id, true);
|
|
3891
|
+
} catch (err) {
|
|
3892
|
+
console.warn("[handlePackages] failed to persist disable state", { id, error: err?.message });
|
|
3893
|
+
}
|
|
3758
3894
|
return { handled: true, response: this.success(pkg) };
|
|
3759
3895
|
}
|
|
3760
3896
|
if (parts.length === 2 && parts[1] === "publish" && m === "POST") {
|
|
3761
3897
|
const id = decodeURIComponent(parts[0]);
|
|
3762
|
-
const metadataService = await this.getService(
|
|
3898
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3763
3899
|
if (metadataService && typeof metadataService.publishPackage === "function") {
|
|
3764
3900
|
const result = await metadataService.publishPackage(id, body || {});
|
|
3765
3901
|
return { handled: true, response: this.success(result) };
|
|
@@ -3768,13 +3904,21 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3768
3904
|
}
|
|
3769
3905
|
if (parts.length === 2 && parts[1] === "revert" && m === "POST") {
|
|
3770
3906
|
const id = decodeURIComponent(parts[0]);
|
|
3771
|
-
const metadataService = await this.getService(
|
|
3907
|
+
const metadataService = await this.getService(import_system2.CoreServiceName.enum.metadata);
|
|
3772
3908
|
if (metadataService && typeof metadataService.revertPackage === "function") {
|
|
3773
3909
|
await metadataService.revertPackage(id);
|
|
3774
3910
|
return { handled: true, response: this.success({ success: true }) };
|
|
3775
3911
|
}
|
|
3776
3912
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
3777
3913
|
}
|
|
3914
|
+
if (parts.length === 2 && parts[1] === "export" && m === "GET") {
|
|
3915
|
+
const id = decodeURIComponent(parts[0]);
|
|
3916
|
+
const manifest = await this.assemblePackageManifest(id, registry, _context);
|
|
3917
|
+
if (!manifest) {
|
|
3918
|
+
return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3919
|
+
}
|
|
3920
|
+
return { handled: true, response: this.success(manifest) };
|
|
3921
|
+
}
|
|
3778
3922
|
if (parts.length === 1 && m === "GET") {
|
|
3779
3923
|
const id = decodeURIComponent(parts[0]);
|
|
3780
3924
|
const pkg = registry.getPackage(id);
|
|
@@ -3792,6 +3936,83 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3792
3936
|
}
|
|
3793
3937
|
return { handled: false };
|
|
3794
3938
|
}
|
|
3939
|
+
/**
|
|
3940
|
+
* Assemble a portable, offline-installable package manifest from the
|
|
3941
|
+
* `sys_metadata` overlay rows bound to `packageId`.
|
|
3942
|
+
*
|
|
3943
|
+
* The resulting shape mirrors what `marketplace-install-local` →
|
|
3944
|
+
* `manifestService.register()` → `engine.registerApp()` consumes:
|
|
3945
|
+
* `{ id, name, version, objects:[…], views:[…], flows:[…], … }`
|
|
3946
|
+
* where each category key is the PLURAL manifest name and its value is
|
|
3947
|
+
* an array of clean metadata bodies (provenance decorations stripped).
|
|
3948
|
+
*
|
|
3949
|
+
* Only the metadata categories that `registerApp` can actually consume
|
|
3950
|
+
* are exported. `datasources` and `emailTemplates` are intentionally
|
|
3951
|
+
* excluded (not registered by the import path). `tools` / `skills` ARE
|
|
3952
|
+
* round-tripped: they are registered by `registerApp` on import and
|
|
3953
|
+
* surfaced by `getMetaItems('tool' | 'skill')` on export.
|
|
3954
|
+
*
|
|
3955
|
+
* @returns the manifest object, or `null` if the package id is unknown
|
|
3956
|
+
* AND has no overlay-authored metadata.
|
|
3957
|
+
*/
|
|
3958
|
+
async assemblePackageManifest(packageId, registry, context) {
|
|
3959
|
+
const protocol = await this.resolveService("protocol");
|
|
3960
|
+
if (!protocol || typeof protocol.getMetaItems !== "function") return null;
|
|
3961
|
+
const organizationId = await this.resolveActiveOrganizationId(context);
|
|
3962
|
+
const PROVENANCE_KEYS = /* @__PURE__ */ new Set([
|
|
3963
|
+
"_packageId",
|
|
3964
|
+
"_packageVersionId",
|
|
3965
|
+
"_provenance",
|
|
3966
|
+
"_state",
|
|
3967
|
+
"_version",
|
|
3968
|
+
"_organizationId",
|
|
3969
|
+
"_source",
|
|
3970
|
+
"_id",
|
|
3971
|
+
"_rowId"
|
|
3972
|
+
]);
|
|
3973
|
+
const clean = (item) => {
|
|
3974
|
+
if (!item || typeof item !== "object") return item;
|
|
3975
|
+
const out = {};
|
|
3976
|
+
for (const [k, v] of Object.entries(item)) {
|
|
3977
|
+
if (k.startsWith("_") || PROVENANCE_KEYS.has(k)) continue;
|
|
3978
|
+
out[k] = v;
|
|
3979
|
+
}
|
|
3980
|
+
return out;
|
|
3981
|
+
};
|
|
3982
|
+
const exportPluralKeys = Object.keys(import_shared2.PLURAL_TO_SINGULAR).filter(
|
|
3983
|
+
(k) => k !== "datasources" && k !== "emailTemplates"
|
|
3984
|
+
);
|
|
3985
|
+
const manifest = {};
|
|
3986
|
+
let total = 0;
|
|
3987
|
+
for (const plural of exportPluralKeys) {
|
|
3988
|
+
const singular = import_shared2.PLURAL_TO_SINGULAR[plural];
|
|
3989
|
+
let items = [];
|
|
3990
|
+
try {
|
|
3991
|
+
const res = await protocol.getMetaItems({ type: singular, packageId, organizationId });
|
|
3992
|
+
items = Array.isArray(res?.items) ? res.items : [];
|
|
3993
|
+
} catch {
|
|
3994
|
+
continue;
|
|
3995
|
+
}
|
|
3996
|
+
if (items.length === 0) continue;
|
|
3997
|
+
manifest[plural] = items.map(clean);
|
|
3998
|
+
total += items.length;
|
|
3999
|
+
}
|
|
4000
|
+
const pkg = (() => {
|
|
4001
|
+
try {
|
|
4002
|
+
return registry?.getPackage?.(packageId);
|
|
4003
|
+
} catch {
|
|
4004
|
+
return void 0;
|
|
4005
|
+
}
|
|
4006
|
+
})();
|
|
4007
|
+
if (total === 0 && !pkg) return null;
|
|
4008
|
+
manifest.id = packageId;
|
|
4009
|
+
manifest.name = pkg?.manifest?.name ?? pkg?.name ?? packageId;
|
|
4010
|
+
manifest.version = pkg?.manifest?.version ?? pkg?.version ?? "1.0.0";
|
|
4011
|
+
if (pkg?.manifest?.label ?? pkg?.label) {
|
|
4012
|
+
manifest.label = pkg?.manifest?.label ?? pkg?.label;
|
|
4013
|
+
}
|
|
4014
|
+
return manifest;
|
|
4015
|
+
}
|
|
3795
4016
|
/**
|
|
3796
4017
|
* Cloud / Environment Control-Plane routes.
|
|
3797
4018
|
*
|
|
@@ -3814,1348 +4035,57 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3814
4035
|
* - DELETE /cloud/environments/:id/packages/:pkgId → uninstall (scope=platform forbidden)
|
|
3815
4036
|
* - POST /cloud/environments/:id/packages/:pkgId/upgrade → upgrade to newer version
|
|
3816
4037
|
*
|
|
3817
|
-
* Driver binding
|
|
3818
|
-
* --------------
|
|
3819
|
-
* Environments are not tied to any specific driver. At provisioning time the
|
|
3820
|
-
* caller passes `driver` (a short name such as `memory`, `turso`, or any
|
|
3821
|
-
* future `sql` / `postgres` driver). The dispatcher validates the name
|
|
3822
|
-
* against the kernel's registered driver services (`driver.<name>`) and
|
|
3823
|
-
* derives an appropriate placeholder `database_url` for the chosen driver.
|
|
3824
|
-
* If `driver` is omitted, the dispatcher auto-selects the first available
|
|
3825
|
-
* in preference order: turso → memory → any other registered driver.
|
|
3826
|
-
*
|
|
3827
|
-
* Backed by ObjectQL sys_environment / sys_environment_credential /
|
|
3828
|
-
* sys_environment_member tables (registered by
|
|
3829
|
-
* `@objectstack/service-tenant`'s `createTenantPlugin`).
|
|
3830
|
-
* Physical database addressing (database_url, database_driver, etc.)
|
|
3831
|
-
* is stored directly on the sys_environment row.
|
|
3832
|
-
*/
|
|
3833
|
-
/**
|
|
3834
|
-
* Resolve the calling user id from the request session, if any.
|
|
3835
|
-
* Returns `undefined` for anonymous calls or when auth is not wired up.
|
|
3836
|
-
*/
|
|
3837
|
-
async resolveActiveOrganizationId(context) {
|
|
3838
|
-
try {
|
|
3839
|
-
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
3840
|
-
const rawHeaders = context.request?.headers;
|
|
3841
|
-
let headers = rawHeaders;
|
|
3842
|
-
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
3843
|
-
try {
|
|
3844
|
-
const h = new Headers();
|
|
3845
|
-
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
3846
|
-
if (v == null) continue;
|
|
3847
|
-
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
3848
|
-
}
|
|
3849
|
-
headers = h;
|
|
3850
|
-
} catch {
|
|
3851
|
-
headers = rawHeaders;
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
3855
|
-
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
3856
|
-
const oid = sessionData?.session?.activeOrganizationId;
|
|
3857
|
-
return typeof oid === "string" && oid.length > 0 ? oid : void 0;
|
|
3858
|
-
} catch {
|
|
3859
|
-
return void 0;
|
|
3860
|
-
}
|
|
3861
|
-
}
|
|
3862
|
-
async resolveCallerUserId(context) {
|
|
3863
|
-
try {
|
|
3864
|
-
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
3865
|
-
const rawHeaders = context.request?.headers;
|
|
3866
|
-
let headers = rawHeaders;
|
|
3867
|
-
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
3868
|
-
try {
|
|
3869
|
-
const h = new Headers();
|
|
3870
|
-
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
3871
|
-
if (v == null) continue;
|
|
3872
|
-
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
3873
|
-
}
|
|
3874
|
-
headers = h;
|
|
3875
|
-
} catch {
|
|
3876
|
-
headers = rawHeaders;
|
|
3877
|
-
}
|
|
3878
|
-
}
|
|
3879
|
-
const sessionData = await (authService?.auth?.api?.getSession ?? authService?.api?.getSession)?.call(
|
|
3880
|
-
authService?.auth?.api ?? authService?.api,
|
|
3881
|
-
{ headers }
|
|
3882
|
-
);
|
|
3883
|
-
return sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
3884
|
-
} catch (e) {
|
|
3885
|
-
return void 0;
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
async handleCloud(path, method, body, query, _context) {
|
|
3889
|
-
const m = method.toUpperCase();
|
|
3890
|
-
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
3891
|
-
const qlService = await this.getObjectQLService();
|
|
3892
|
-
const ql = qlService ?? await this.resolveService("objectql");
|
|
3893
|
-
if (!ql) {
|
|
3894
|
-
return { handled: true, response: this.error("Project service not available (ObjectQL missing)", 503) };
|
|
3895
|
-
}
|
|
3896
|
-
const ENV = "sys_environment";
|
|
3897
|
-
const CRED = "sys_environment_credential";
|
|
3898
|
-
const MEM = "sys_environment_member";
|
|
3899
|
-
const PKG_INSTALL = "sys_package_installation";
|
|
3900
|
-
const PKG = "sys_package";
|
|
3901
|
-
const PKG_VERSION = "sys_package_version";
|
|
3902
|
-
const ensureSysPackage = async (manifestId, ownerOrgId, createdBy, manifest) => {
|
|
3903
|
-
const existing = await ql.findOne(PKG, { where: { manifest_id: manifestId } });
|
|
3904
|
-
if (existing?.id) return existing.id;
|
|
3905
|
-
const id = randomUUID();
|
|
3906
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3907
|
-
await ql.insert(PKG, {
|
|
3908
|
-
id,
|
|
3909
|
-
manifest_id: manifestId,
|
|
3910
|
-
owner_org_id: ownerOrgId,
|
|
3911
|
-
display_name: manifest?.name ?? manifestId,
|
|
3912
|
-
description: manifest?.description ?? null,
|
|
3913
|
-
visibility: "private",
|
|
3914
|
-
created_by: createdBy,
|
|
3915
|
-
created_at: nowIso,
|
|
3916
|
-
updated_at: nowIso
|
|
3917
|
-
});
|
|
3918
|
-
return id;
|
|
3919
|
-
};
|
|
3920
|
-
const ensureSysPackageVersion = async (packageId, version, createdBy, manifest) => {
|
|
3921
|
-
const existing = await ql.findOne(PKG_VERSION, {
|
|
3922
|
-
where: { package_id: packageId, version }
|
|
3923
|
-
});
|
|
3924
|
-
if (existing?.id) return existing.id;
|
|
3925
|
-
const id = randomUUID();
|
|
3926
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3927
|
-
await ql.insert(PKG_VERSION, {
|
|
3928
|
-
id,
|
|
3929
|
-
package_id: packageId,
|
|
3930
|
-
version,
|
|
3931
|
-
status: "published",
|
|
3932
|
-
manifest_json: manifest ? JSON.stringify(manifest) : null,
|
|
3933
|
-
is_pre_release: false,
|
|
3934
|
-
published_at: nowIso,
|
|
3935
|
-
published_by: createdBy,
|
|
3936
|
-
created_by: createdBy,
|
|
3937
|
-
created_at: nowIso,
|
|
3938
|
-
updated_at: nowIso
|
|
3939
|
-
});
|
|
3940
|
-
return id;
|
|
3941
|
-
};
|
|
3942
|
-
const findInstallByManifestId = async (envId, manifestId) => {
|
|
3943
|
-
const pkgRow = await ql.findOne(PKG, { where: { manifest_id: manifestId } });
|
|
3944
|
-
if (!pkgRow?.id) return null;
|
|
3945
|
-
return await ql.findOne(PKG_INSTALL, {
|
|
3946
|
-
where: { environment_id: envId, package_id: pkgRow.id }
|
|
3947
|
-
});
|
|
3948
|
-
};
|
|
3949
|
-
const toShortName = (driverId) => {
|
|
3950
|
-
const prefix = "com.objectstack.driver.";
|
|
3951
|
-
return driverId.startsWith(prefix) ? driverId.slice(prefix.length) : driverId;
|
|
3952
|
-
};
|
|
3953
|
-
const listRegisteredDrivers = () => {
|
|
3954
|
-
const services = this.getServicesMap();
|
|
3955
|
-
const registry = services["project-provisioning-adapters"];
|
|
3956
|
-
if (registry && typeof registry.list === "function") {
|
|
3957
|
-
try {
|
|
3958
|
-
const adapters = registry.list();
|
|
3959
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3960
|
-
const drivers2 = [];
|
|
3961
|
-
for (const adapter of adapters ?? []) {
|
|
3962
|
-
const name = adapter?.driver;
|
|
3963
|
-
if (!name || seen.has(name)) continue;
|
|
3964
|
-
seen.add(name);
|
|
3965
|
-
drivers2.push({ name, driverId: `com.objectstack.driver.${name}` });
|
|
3966
|
-
}
|
|
3967
|
-
if (drivers2.length > 0) return drivers2;
|
|
3968
|
-
} catch {
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
const drivers = [];
|
|
3972
|
-
for (const [serviceKey, svc] of Object.entries(services)) {
|
|
3973
|
-
if (!serviceKey.startsWith("driver.")) continue;
|
|
3974
|
-
const raw = serviceKey.slice("driver.".length);
|
|
3975
|
-
if (!raw || raw === "unknown") continue;
|
|
3976
|
-
const driverId = svc?.name ?? raw;
|
|
3977
|
-
drivers.push({ name: toShortName(driverId), driverId });
|
|
3978
|
-
}
|
|
3979
|
-
return drivers;
|
|
3980
|
-
};
|
|
3981
|
-
const resolveDriver = (requested) => {
|
|
3982
|
-
const registered = listRegisteredDrivers();
|
|
3983
|
-
if (requested) {
|
|
3984
|
-
const wanted = String(requested).toLowerCase();
|
|
3985
|
-
return registered.find((d) => d.name === wanted || d.driverId === wanted);
|
|
3986
|
-
}
|
|
3987
|
-
return registered.find((d) => d.name === "turso") ?? registered.find((d) => d.name === "memory") ?? registered[0];
|
|
3988
|
-
};
|
|
3989
|
-
const buildDatabaseUrl = (driverName, environmentId) => {
|
|
3990
|
-
const dbName = `env-${environmentId}`;
|
|
3991
|
-
switch (driverName) {
|
|
3992
|
-
case "memory":
|
|
3993
|
-
return `memory://${dbName}`;
|
|
3994
|
-
case "turso":
|
|
3995
|
-
return `libsql://${dbName}.mock-turso.local`;
|
|
3996
|
-
default:
|
|
3997
|
-
return `${driverName}://${dbName}`;
|
|
3998
|
-
}
|
|
3999
|
-
};
|
|
4000
|
-
const getRealAdapter = async (driverName) => {
|
|
4001
|
-
try {
|
|
4002
|
-
const registry = await this.resolveService("project-provisioning-adapters");
|
|
4003
|
-
const aliases = { sql: "sqlite" };
|
|
4004
|
-
const effective = aliases[driverName] ?? driverName;
|
|
4005
|
-
return registry?.get?.(effective) ?? registry?.get?.(driverName);
|
|
4006
|
-
} catch {
|
|
4007
|
-
return void 0;
|
|
4008
|
-
}
|
|
4009
|
-
};
|
|
4010
|
-
const findOne = async (obj, where) => {
|
|
4011
|
-
let rows = await ql.find(obj, { where });
|
|
4012
|
-
if (rows && rows.value) rows = rows.value;
|
|
4013
|
-
if (!Array.isArray(rows)) return void 0;
|
|
4014
|
-
return rows[0];
|
|
4015
|
-
};
|
|
4016
|
-
const cleanProjectRow = (row) => {
|
|
4017
|
-
if (!row) return row;
|
|
4018
|
-
let metadata = row.metadata;
|
|
4019
|
-
if (typeof metadata === "string") {
|
|
4020
|
-
try {
|
|
4021
|
-
metadata = JSON.parse(metadata);
|
|
4022
|
-
} catch {
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
return { ...row, metadata };
|
|
4026
|
-
};
|
|
4027
|
-
try {
|
|
4028
|
-
if (parts.length === 1 && parts[0] === "drivers" && m === "GET") {
|
|
4029
|
-
const drivers = listRegisteredDrivers();
|
|
4030
|
-
return { handled: true, response: this.success({ drivers, total: drivers.length }) };
|
|
4031
|
-
}
|
|
4032
|
-
if (parts.length === 1 && parts[0] === "templates" && m === "GET") {
|
|
4033
|
-
try {
|
|
4034
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4035
|
-
const templates = seeder?.listTemplates?.() ?? [];
|
|
4036
|
-
return { handled: true, response: this.success({ templates, total: templates.length }) };
|
|
4037
|
-
} catch (err) {
|
|
4038
|
-
try {
|
|
4039
|
-
console.error("[HttpDispatcher] /cloud/templates: failed to resolve template-seeder:", err?.message ?? err);
|
|
4040
|
-
} catch {
|
|
4041
|
-
}
|
|
4042
|
-
return { handled: true, response: this.success({ templates: [], total: 0 }) };
|
|
4043
|
-
}
|
|
4044
|
-
}
|
|
4045
|
-
if (parts.length === 3 && parts[0] === "admin" && parts[1] === "platform-sso" && parts[2] === "backfill" && m === "POST") {
|
|
4046
|
-
const baseSecret = ((0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
4047
|
-
if (!baseSecret) {
|
|
4048
|
-
return { handled: true, response: this.error("OS_AUTH_SECRET not configured on this worker", 503) };
|
|
4049
|
-
}
|
|
4050
|
-
const rawHeaders = _context?.request?.headers;
|
|
4051
|
-
let authHeader;
|
|
4052
|
-
if (rawHeaders && typeof rawHeaders.get === "function") {
|
|
4053
|
-
authHeader = rawHeaders.get("authorization") ?? void 0;
|
|
4054
|
-
} else if (rawHeaders && typeof rawHeaders === "object") {
|
|
4055
|
-
authHeader = rawHeaders["authorization"] ?? rawHeaders["Authorization"];
|
|
4056
|
-
}
|
|
4057
|
-
const presented = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
|
|
4058
|
-
if (!presented || presented !== baseSecret) {
|
|
4059
|
-
return { handled: true, response: this.error("forbidden: Bearer token must match OS_AUTH_SECRET", 403) };
|
|
4060
|
-
}
|
|
4061
|
-
try {
|
|
4062
|
-
const { backfillPlatformSsoClients: backfillPlatformSsoClients2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
4063
|
-
const result = await backfillPlatformSsoClients2({
|
|
4064
|
-
ql,
|
|
4065
|
-
baseSecret,
|
|
4066
|
-
logger: console
|
|
4067
|
-
});
|
|
4068
|
-
let sample = [];
|
|
4069
|
-
let total = 0;
|
|
4070
|
-
try {
|
|
4071
|
-
const rows = await ql.find("sys_oauth_application", { limit: 5 }, { context: { isSystem: true } });
|
|
4072
|
-
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
4073
|
-
sample = list;
|
|
4074
|
-
total = typeof rows?.total === "number" ? rows.total : list.length;
|
|
4075
|
-
} catch (e) {
|
|
4076
|
-
sample = [{ _readErr: e?.message ?? String(e) }];
|
|
4077
|
-
}
|
|
4078
|
-
return { handled: true, response: this.success({ ...result, total, sample }) };
|
|
4079
|
-
} catch (err) {
|
|
4080
|
-
return { handled: true, response: this.error(`backfill failed: ${err?.message ?? String(err)}`, 500) };
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
if (parts.length === 1 && parts[0] === "projects" && m === "GET") {
|
|
4084
|
-
const where = {};
|
|
4085
|
-
if (query?.organizationId) where.organization_id = query.organizationId;
|
|
4086
|
-
if (query?.status) where.status = query.status;
|
|
4087
|
-
let rows = await ql.find(ENV, Object.keys(where).length ? { where } : void 0);
|
|
4088
|
-
if (rows && rows.value) rows = rows.value;
|
|
4089
|
-
const projects = (Array.isArray(rows) ? rows : []).map(cleanProjectRow);
|
|
4090
|
-
return { handled: true, response: this.success({ projects, total: projects.length }) };
|
|
4091
|
-
}
|
|
4092
|
-
if (parts.length === 1 && parts[0] === "projects" && m === "POST") {
|
|
4093
|
-
const req = body || {};
|
|
4094
|
-
if (req.organization_id === "__session__" || req.created_by === "__session__") {
|
|
4095
|
-
try {
|
|
4096
|
-
const userId = await this.resolveCallerUserId(_context);
|
|
4097
|
-
if (req.created_by === "__session__") {
|
|
4098
|
-
req.created_by = userId ?? "system";
|
|
4099
|
-
}
|
|
4100
|
-
if (req.organization_id === "__session__") {
|
|
4101
|
-
const authService = await this.resolveService(import_system.CoreServiceName.enum.auth);
|
|
4102
|
-
const rawHeaders = _context?.request?.headers;
|
|
4103
|
-
let headers = rawHeaders;
|
|
4104
|
-
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
4105
|
-
const h = new Headers();
|
|
4106
|
-
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
4107
|
-
if (v == null) continue;
|
|
4108
|
-
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
4109
|
-
}
|
|
4110
|
-
headers = h;
|
|
4111
|
-
}
|
|
4112
|
-
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
4113
|
-
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
4114
|
-
req.organization_id = sessionData?.session?.activeOrganizationId ?? void 0;
|
|
4115
|
-
}
|
|
4116
|
-
} catch {
|
|
4117
|
-
}
|
|
4118
|
-
}
|
|
4119
|
-
if (!req.organization_id || !req.display_name) {
|
|
4120
|
-
return { handled: true, response: this.error("organization_id and display_name are required", 400) };
|
|
4121
|
-
}
|
|
4122
|
-
const environmentId = randomUUID();
|
|
4123
|
-
const credentialId = randomUUID();
|
|
4124
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4125
|
-
const resolved = resolveDriver(req.driver);
|
|
4126
|
-
if (!resolved) {
|
|
4127
|
-
const available = listRegisteredDrivers().map((d) => d.name);
|
|
4128
|
-
if (req.driver) {
|
|
4129
|
-
return {
|
|
4130
|
-
handled: true,
|
|
4131
|
-
response: this.error(
|
|
4132
|
-
`Unknown driver '${req.driver}'. Available drivers: [${available.join(", ") || "none"}]`,
|
|
4133
|
-
400
|
|
4134
|
-
)
|
|
4135
|
-
};
|
|
4136
|
-
}
|
|
4137
|
-
return {
|
|
4138
|
-
handled: true,
|
|
4139
|
-
response: this.error(
|
|
4140
|
-
"No ObjectQL driver is registered. Register at least one DriverPlugin (e.g. InMemoryDriver or SqlDriver).",
|
|
4141
|
-
503
|
|
4142
|
-
)
|
|
4143
|
-
};
|
|
4144
|
-
}
|
|
4145
|
-
const driver = resolved.name;
|
|
4146
|
-
let plaintextSecret = `mock-token-${environmentId}`;
|
|
4147
|
-
let computedHostname = req.hostname;
|
|
4148
|
-
if (!computedHostname) {
|
|
4149
|
-
const shortId = environmentId.slice(0, 8);
|
|
4150
|
-
try {
|
|
4151
|
-
const orgRow = await findOne("sys_organization", { id: req.organization_id });
|
|
4152
|
-
const orgSlug = orgRow?.slug || req.organization_id;
|
|
4153
|
-
const rootDomain = (0, import_core2.getEnv)("OS_ROOT_DOMAIN") ?? (0, import_core2.getEnv)("ROOT_DOMAIN", "objectstack.app");
|
|
4154
|
-
computedHostname = `${orgSlug}-${shortId}.${rootDomain}`;
|
|
4155
|
-
} catch {
|
|
4156
|
-
computedHostname = `${req.organization_id}-${shortId}.objectstack.app`;
|
|
4157
|
-
}
|
|
4158
|
-
}
|
|
4159
|
-
try {
|
|
4160
|
-
const existing = await findOne("sys_environment", {
|
|
4161
|
-
hostname: computedHostname
|
|
4162
|
-
});
|
|
4163
|
-
if (existing && existing.id !== environmentId) {
|
|
4164
|
-
return {
|
|
4165
|
-
handled: true,
|
|
4166
|
-
response: this.error(
|
|
4167
|
-
`Hostname '${computedHostname}' is already in use by another project.`,
|
|
4168
|
-
409,
|
|
4169
|
-
{ code: "HOSTNAME_TAKEN", hostname: computedHostname }
|
|
4170
|
-
)
|
|
4171
|
-
};
|
|
4172
|
-
}
|
|
4173
|
-
} catch {
|
|
4174
|
-
}
|
|
4175
|
-
const baseMetadata = { ...req.metadata ?? {} };
|
|
4176
|
-
const simulateFailure = Boolean(baseMetadata.__simulateFailure);
|
|
4177
|
-
const simulateDelayMs = Number(baseMetadata.__simulateDelayMs ?? 1500);
|
|
4178
|
-
try {
|
|
4179
|
-
let ownerUserId = req.created_by && req.created_by !== "system" ? String(req.created_by) : void 0;
|
|
4180
|
-
if (!ownerUserId) {
|
|
4181
|
-
ownerUserId = await this.resolveCallerUserId(_context);
|
|
4182
|
-
}
|
|
4183
|
-
if (ownerUserId) {
|
|
4184
|
-
const userRow = await ql.find("sys_user", { where: { id: ownerUserId } });
|
|
4185
|
-
const userRows = Array.isArray(userRow) ? userRow : userRow?.value ?? [];
|
|
4186
|
-
const u = Array.isArray(userRows) && userRows.length > 0 ? userRows[0] : null;
|
|
4187
|
-
if (u?.email) {
|
|
4188
|
-
baseMetadata.ownerSeed = {
|
|
4189
|
-
userId: String(ownerUserId),
|
|
4190
|
-
email: String(u.email),
|
|
4191
|
-
name: u.name ? String(u.name) : null,
|
|
4192
|
-
image: u.image ? String(u.image) : null
|
|
4193
|
-
};
|
|
4194
|
-
}
|
|
4195
|
-
}
|
|
4196
|
-
} catch {
|
|
4197
|
-
}
|
|
4198
|
-
try {
|
|
4199
|
-
const orgRow = await ql.find("sys_organization", { where: { id: req.organization_id } });
|
|
4200
|
-
const orgRows = Array.isArray(orgRow) ? orgRow : orgRow?.value ?? [];
|
|
4201
|
-
const org = Array.isArray(orgRows) && orgRows.length > 0 ? orgRows[0] : null;
|
|
4202
|
-
if (org?.id && org?.name) {
|
|
4203
|
-
baseMetadata.orgSeed = {
|
|
4204
|
-
id: String(org.id),
|
|
4205
|
-
name: String(org.name),
|
|
4206
|
-
slug: org.slug ? String(org.slug) : null,
|
|
4207
|
-
logo: org.logo ? String(org.logo) : null
|
|
4208
|
-
};
|
|
4209
|
-
}
|
|
4210
|
-
} catch {
|
|
4211
|
-
}
|
|
4212
|
-
await ql.insert(ENV, {
|
|
4213
|
-
id: environmentId,
|
|
4214
|
-
organization_id: req.organization_id,
|
|
4215
|
-
display_name: req.display_name,
|
|
4216
|
-
is_default: req.is_default ?? false,
|
|
4217
|
-
is_system: req.is_system ?? false,
|
|
4218
|
-
plan: req.plan ?? "free",
|
|
4219
|
-
status: "provisioning",
|
|
4220
|
-
created_by: req.created_by ?? "system",
|
|
4221
|
-
metadata: JSON.stringify(baseMetadata),
|
|
4222
|
-
created_at: nowIso,
|
|
4223
|
-
updated_at: nowIso,
|
|
4224
|
-
database_url: null,
|
|
4225
|
-
database_driver: driver,
|
|
4226
|
-
storage_limit_mb: req.storage_limit_mb ?? 1024,
|
|
4227
|
-
provisioned_at: null,
|
|
4228
|
-
hostname: computedHostname,
|
|
4229
|
-
visibility: (() => {
|
|
4230
|
-
const raw = String(req.visibility ?? "private");
|
|
4231
|
-
return raw === "unlisted" ? "private" : raw;
|
|
4232
|
-
})()
|
|
4233
|
-
});
|
|
4234
|
-
try {
|
|
4235
|
-
const { seedPlatformSsoClient: seedPlatformSsoClient2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
4236
|
-
const baseSecret = ((0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
4237
|
-
if (baseSecret) {
|
|
4238
|
-
await seedPlatformSsoClient2({
|
|
4239
|
-
ql,
|
|
4240
|
-
environmentId,
|
|
4241
|
-
hostname: computedHostname,
|
|
4242
|
-
baseSecret,
|
|
4243
|
-
logger: console
|
|
4244
|
-
});
|
|
4245
|
-
}
|
|
4246
|
-
} catch (ssoErr) {
|
|
4247
|
-
console.warn?.("[http-dispatcher] platform SSO seed failed (non-fatal)", {
|
|
4248
|
-
environmentId,
|
|
4249
|
-
error: ssoErr?.message
|
|
4250
|
-
});
|
|
4251
|
-
}
|
|
4252
|
-
const runProvisioning = async () => {
|
|
4253
|
-
try {
|
|
4254
|
-
if (simulateDelayMs > 0) {
|
|
4255
|
-
await new Promise((r) => setTimeout(r, simulateDelayMs));
|
|
4256
|
-
}
|
|
4257
|
-
if (simulateFailure) {
|
|
4258
|
-
throw new Error("Simulated provisioning failure (metadata.__simulateFailure=true)");
|
|
4259
|
-
}
|
|
4260
|
-
let databaseUrl;
|
|
4261
|
-
try {
|
|
4262
|
-
const adapter = await getRealAdapter(driver);
|
|
4263
|
-
if (adapter) {
|
|
4264
|
-
const result = await adapter.createDatabase({
|
|
4265
|
-
environmentId,
|
|
4266
|
-
databaseName: `p-${environmentId.replace(/-/g, "").slice(0, 24)}`,
|
|
4267
|
-
region: "us-east-1",
|
|
4268
|
-
storageLimitMb: req.storage_limit_mb ?? 1024
|
|
4269
|
-
});
|
|
4270
|
-
databaseUrl = result.databaseUrl;
|
|
4271
|
-
if (result.plaintextSecret) plaintextSecret = result.plaintextSecret;
|
|
4272
|
-
} else {
|
|
4273
|
-
databaseUrl = buildDatabaseUrl(driver, environmentId);
|
|
4274
|
-
}
|
|
4275
|
-
} catch (adapterErr) {
|
|
4276
|
-
throw adapterErr instanceof Error ? adapterErr : new Error(String(adapterErr));
|
|
4277
|
-
}
|
|
4278
|
-
const seedStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4279
|
-
await ql.update(
|
|
4280
|
-
ENV,
|
|
4281
|
-
{
|
|
4282
|
-
database_url: databaseUrl,
|
|
4283
|
-
updated_at: seedStartedAt
|
|
4284
|
-
},
|
|
4285
|
-
{ where: { id: environmentId } }
|
|
4286
|
-
);
|
|
4287
|
-
await ql.insert(CRED, {
|
|
4288
|
-
id: credentialId,
|
|
4289
|
-
environment_id: environmentId,
|
|
4290
|
-
secret_ciphertext: plaintextSecret,
|
|
4291
|
-
encryption_key_id: "noop",
|
|
4292
|
-
authorization: "full_access",
|
|
4293
|
-
status: "active",
|
|
4294
|
-
created_at: seedStartedAt,
|
|
4295
|
-
updated_at: seedStartedAt
|
|
4296
|
-
});
|
|
4297
|
-
const templateId = req.template_id ?? "blank";
|
|
4298
|
-
if (templateId !== "blank") {
|
|
4299
|
-
try {
|
|
4300
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4301
|
-
if (seeder) {
|
|
4302
|
-
await seeder.seed({ environmentId, templateId });
|
|
4303
|
-
}
|
|
4304
|
-
} catch (seedErr) {
|
|
4305
|
-
const seedMessage = seedErr instanceof Error ? seedErr.message : String(seedErr);
|
|
4306
|
-
try {
|
|
4307
|
-
const existing = await findOne(ENV, { id: environmentId });
|
|
4308
|
-
const existingMeta = typeof existing?.metadata === "string" ? JSON.parse(existing.metadata) : existing?.metadata ?? {};
|
|
4309
|
-
await ql.update(
|
|
4310
|
-
ENV,
|
|
4311
|
-
{
|
|
4312
|
-
metadata: JSON.stringify({
|
|
4313
|
-
...existingMeta,
|
|
4314
|
-
templateSeedError: { message: seedMessage, templateId }
|
|
4315
|
-
})
|
|
4316
|
-
},
|
|
4317
|
-
{ where: { id: environmentId } }
|
|
4318
|
-
);
|
|
4319
|
-
} catch {
|
|
4320
|
-
}
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
const artifactPathRaw = baseMetadata.artifact_path;
|
|
4324
|
-
if (typeof artifactPathRaw === "string" && artifactPathRaw.length > 0) {
|
|
4325
|
-
try {
|
|
4326
|
-
const path2 = await import("path");
|
|
4327
|
-
const { isHttpUrl: isHttpUrl2, loadArtifactBundle: loadArtifactBundle2 } = await Promise.resolve().then(() => (init_load_artifact_bundle(), load_artifact_bundle_exports));
|
|
4328
|
-
const root = process.env.OS_PROJECT_ARTIFACT_ROOT ?? process.cwd();
|
|
4329
|
-
const resolved2 = isHttpUrl2(artifactPathRaw) ? artifactPathRaw : path2.isAbsolute(artifactPathRaw) ? artifactPathRaw : path2.resolve(root, artifactPathRaw);
|
|
4330
|
-
const bundle = await loadArtifactBundle2(resolved2, { tag: "[bind-artifact]" });
|
|
4331
|
-
if (!bundle) {
|
|
4332
|
-
throw new Error(`failed to load artifact bundle at '${resolved2}'`);
|
|
4333
|
-
}
|
|
4334
|
-
const seeder = await this.resolveService("template-seeder");
|
|
4335
|
-
if (seeder?.seedBundle) {
|
|
4336
|
-
await seeder.seedBundle({ environmentId, bundle });
|
|
4337
|
-
} else {
|
|
4338
|
-
throw new Error("template-seeder.seedBundle is unavailable");
|
|
4339
|
-
}
|
|
4340
|
-
} catch (bindErr) {
|
|
4341
|
-
const bindMessage = bindErr instanceof Error ? bindErr.message : String(bindErr);
|
|
4342
|
-
try {
|
|
4343
|
-
const existing = await findOne(ENV, { id: environmentId });
|
|
4344
|
-
const existingMeta = typeof existing?.metadata === "string" ? JSON.parse(existing.metadata) : existing?.metadata ?? {};
|
|
4345
|
-
await ql.update(
|
|
4346
|
-
ENV,
|
|
4347
|
-
{
|
|
4348
|
-
metadata: JSON.stringify({
|
|
4349
|
-
...existingMeta,
|
|
4350
|
-
artifactBindError: { message: bindMessage, artifactPath: artifactPathRaw }
|
|
4351
|
-
})
|
|
4352
|
-
},
|
|
4353
|
-
{ where: { id: environmentId } }
|
|
4354
|
-
);
|
|
4355
|
-
} catch {
|
|
4356
|
-
}
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4360
|
-
await ql.update(
|
|
4361
|
-
ENV,
|
|
4362
|
-
{
|
|
4363
|
-
status: "active",
|
|
4364
|
-
provisioned_at: finishedAt,
|
|
4365
|
-
updated_at: finishedAt
|
|
4366
|
-
},
|
|
4367
|
-
{ where: { id: environmentId } }
|
|
4368
|
-
);
|
|
4369
|
-
} catch (err) {
|
|
4370
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4371
|
-
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4372
|
-
await ql.update(
|
|
4373
|
-
ENV,
|
|
4374
|
-
{
|
|
4375
|
-
status: "failed",
|
|
4376
|
-
metadata: JSON.stringify({
|
|
4377
|
-
...baseMetadata,
|
|
4378
|
-
provisioningError: { message, failedAt }
|
|
4379
|
-
}),
|
|
4380
|
-
updated_at: failedAt
|
|
4381
|
-
},
|
|
4382
|
-
{ where: { id: environmentId } }
|
|
4383
|
-
);
|
|
4384
|
-
}
|
|
4385
|
-
};
|
|
4386
|
-
const provisionSyncEnv = process.env.OS_PROVISION_SYNC;
|
|
4387
|
-
const onServerless = !!(process.env.VERCEL || process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.NETLIFY || process.env.CF_PAGES);
|
|
4388
|
-
const syncProvisioning = provisionSyncEnv === void 0 ? onServerless : provisionSyncEnv !== "0" && provisionSyncEnv !== "false";
|
|
4389
|
-
if (syncProvisioning) {
|
|
4390
|
-
await runProvisioning();
|
|
4391
|
-
} else {
|
|
4392
|
-
void runProvisioning();
|
|
4393
|
-
}
|
|
4394
|
-
const project = cleanProjectRow(await findOne(ENV, { id: environmentId }));
|
|
4395
|
-
const res = this.success({ project });
|
|
4396
|
-
res.status = syncProvisioning ? 201 : 202;
|
|
4397
|
-
return { handled: true, response: res };
|
|
4398
|
-
}
|
|
4399
|
-
if (parts.length === 2 && parts[0] === "projects") {
|
|
4400
|
-
const id = decodeURIComponent(parts[1]);
|
|
4401
|
-
if (m === "GET") {
|
|
4402
|
-
const envRow = await findOne(ENV, { id });
|
|
4403
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4404
|
-
const credRow = await findOne(CRED, { environment_id: id, status: "active" });
|
|
4405
|
-
const callerUserId = await this.resolveCallerUserId(_context);
|
|
4406
|
-
const membership = callerUserId ? await findOne(MEM, { environment_id: id, user_id: callerUserId }) : await findOne(MEM, { environment_id: id });
|
|
4407
|
-
const credMeta = credRow ? {
|
|
4408
|
-
id: credRow.id,
|
|
4409
|
-
status: credRow.status,
|
|
4410
|
-
authorization: credRow.authorization,
|
|
4411
|
-
activatedAt: credRow.created_at,
|
|
4412
|
-
expiresAt: credRow.expires_at
|
|
4413
|
-
} : void 0;
|
|
4414
|
-
const project = cleanProjectRow(envRow);
|
|
4415
|
-
const database = project.database_url ? {
|
|
4416
|
-
driver: project.database_driver,
|
|
4417
|
-
database_name: `env-${project.id}`,
|
|
4418
|
-
database_url: project.database_url,
|
|
4419
|
-
storage_limit_mb: project.storage_limit_mb,
|
|
4420
|
-
provisioned_at: project.provisioned_at
|
|
4421
|
-
} : void 0;
|
|
4422
|
-
return {
|
|
4423
|
-
handled: true,
|
|
4424
|
-
response: this.success({ project, database, credential: credMeta, membership })
|
|
4425
|
-
};
|
|
4426
|
-
}
|
|
4427
|
-
if (m === "PATCH") {
|
|
4428
|
-
const patch = {};
|
|
4429
|
-
if (body?.display_name !== void 0) patch.display_name = body.display_name;
|
|
4430
|
-
if (body?.plan !== void 0) patch.plan = body.plan;
|
|
4431
|
-
if (body?.status !== void 0) patch.status = body.status;
|
|
4432
|
-
if (body?.is_default !== void 0) patch.is_default = body.is_default;
|
|
4433
|
-
if (body?.visibility !== void 0) {
|
|
4434
|
-
let v = String(body.visibility);
|
|
4435
|
-
if (v === "unlisted") v = "private";
|
|
4436
|
-
if (!["private", "public"].includes(v)) {
|
|
4437
|
-
return { handled: true, response: this.error(`Invalid visibility '${v}' (expected private | public)`, 400) };
|
|
4438
|
-
}
|
|
4439
|
-
patch.visibility = v;
|
|
4440
|
-
}
|
|
4441
|
-
if (body?.metadata !== void 0) patch.metadata = JSON.stringify(body.metadata);
|
|
4442
|
-
patch.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4443
|
-
await ql.update(ENV, patch, { where: { id } });
|
|
4444
|
-
const envRow = await findOne(ENV, { id });
|
|
4445
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4446
|
-
return { handled: true, response: this.success({ project: cleanProjectRow(envRow) }) };
|
|
4447
|
-
}
|
|
4448
|
-
if (m === "DELETE") {
|
|
4449
|
-
const force = query?.force === "1" || query?.force === "true" || body?.force === true;
|
|
4450
|
-
const result = await this.deleteProjectCascade(id, { ql, findOne, getRealAdapter, force });
|
|
4451
|
-
if (!result.ok) {
|
|
4452
|
-
return { handled: true, response: this.error(result.error ?? "Delete failed", result.status ?? 500) };
|
|
4453
|
-
}
|
|
4454
|
-
return { handled: true, response: this.success({ deleted: true, environmentId: id, warnings: result.warnings }) };
|
|
4455
|
-
}
|
|
4456
|
-
}
|
|
4457
|
-
if (parts.length === 2 && parts[0] === "organizations" && m === "DELETE") {
|
|
4458
|
-
const orgId = decodeURIComponent(parts[1]);
|
|
4459
|
-
let projectRows = [];
|
|
4460
|
-
try {
|
|
4461
|
-
let rows = await ql.find(ENV, { where: { organization_id: orgId } });
|
|
4462
|
-
if (rows && rows.value) rows = rows.value;
|
|
4463
|
-
projectRows = Array.isArray(rows) ? rows : [];
|
|
4464
|
-
} catch {
|
|
4465
|
-
projectRows = [];
|
|
4466
|
-
}
|
|
4467
|
-
const warnings = [];
|
|
4468
|
-
let deletedProjects = 0;
|
|
4469
|
-
for (const row of projectRows) {
|
|
4470
|
-
const pid = row?.id;
|
|
4471
|
-
if (!pid) continue;
|
|
4472
|
-
try {
|
|
4473
|
-
const r = await this.deleteProjectCascade(pid, { ql, findOne, getRealAdapter, force: true });
|
|
4474
|
-
if (r.ok) deletedProjects++;
|
|
4475
|
-
if (r.warnings?.length) warnings.push(...r.warnings);
|
|
4476
|
-
if (!r.ok && r.error) warnings.push(`Project ${pid}: ${r.error}`);
|
|
4477
|
-
} catch (err) {
|
|
4478
|
-
warnings.push(
|
|
4479
|
-
`Failed to delete project ${pid}: ${err instanceof Error ? err.message : String(err)}`
|
|
4480
|
-
);
|
|
4481
|
-
}
|
|
4482
|
-
}
|
|
4483
|
-
let orgDeleted = false;
|
|
4484
|
-
try {
|
|
4485
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
4486
|
-
const fn = authService?.api?.deleteOrganization;
|
|
4487
|
-
if (typeof fn === "function") {
|
|
4488
|
-
await fn.call(authService.api, {
|
|
4489
|
-
body: { organizationId: orgId },
|
|
4490
|
-
headers: _context?.request?.headers
|
|
4491
|
-
});
|
|
4492
|
-
orgDeleted = true;
|
|
4493
|
-
}
|
|
4494
|
-
} catch (err) {
|
|
4495
|
-
warnings.push(
|
|
4496
|
-
`auth.deleteOrganization failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4497
|
-
);
|
|
4498
|
-
}
|
|
4499
|
-
if (!orgDeleted) {
|
|
4500
|
-
try {
|
|
4501
|
-
await ql.delete("sys_organization", { where: { id: orgId } });
|
|
4502
|
-
orgDeleted = true;
|
|
4503
|
-
} catch (err) {
|
|
4504
|
-
warnings.push(
|
|
4505
|
-
`Failed to delete sys_organization row: ${err instanceof Error ? err.message : String(err)}`
|
|
4506
|
-
);
|
|
4507
|
-
}
|
|
4508
|
-
}
|
|
4509
|
-
return {
|
|
4510
|
-
handled: true,
|
|
4511
|
-
response: this.success({
|
|
4512
|
-
deleted: orgDeleted,
|
|
4513
|
-
organizationId: orgId,
|
|
4514
|
-
deletedProjects,
|
|
4515
|
-
warnings
|
|
4516
|
-
})
|
|
4517
|
-
};
|
|
4518
|
-
}
|
|
4519
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "hostname" && (m === "POST" || m === "PUT")) {
|
|
4520
|
-
const id = decodeURIComponent(parts[1]);
|
|
4521
|
-
const hostname = body?.hostname;
|
|
4522
|
-
if (!hostname || typeof hostname !== "string") {
|
|
4523
|
-
return { handled: true, response: this.error("hostname is required", 400) };
|
|
4524
|
-
}
|
|
4525
|
-
const normalized = hostname.trim().toLowerCase();
|
|
4526
|
-
if (!/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/.test(normalized)) {
|
|
4527
|
-
return { handled: true, response: this.error("Invalid hostname format", 400) };
|
|
4528
|
-
}
|
|
4529
|
-
const envRow = await findOne(ENV, { id });
|
|
4530
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4531
|
-
let existing;
|
|
4532
|
-
try {
|
|
4533
|
-
const rows = await ql.find(ENV, { where: { hostname: normalized } });
|
|
4534
|
-
const arr = Array.isArray(rows) ? rows : rows?.value ?? [];
|
|
4535
|
-
existing = arr.find((r) => r.id !== id);
|
|
4536
|
-
} catch {
|
|
4537
|
-
}
|
|
4538
|
-
if (existing) {
|
|
4539
|
-
return {
|
|
4540
|
-
handled: true,
|
|
4541
|
-
response: this.error(
|
|
4542
|
-
`Hostname '${normalized}' is already in use by another project.`,
|
|
4543
|
-
409,
|
|
4544
|
-
{ code: "HOSTNAME_TAKEN", hostname: normalized }
|
|
4545
|
-
)
|
|
4546
|
-
};
|
|
4547
|
-
}
|
|
4548
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4549
|
-
await ql.update(ENV, { hostname: normalized, updated_at: updatedAt }, { where: { id } });
|
|
4550
|
-
if (this.envRegistry?.invalidate) {
|
|
4551
|
-
try {
|
|
4552
|
-
await this.envRegistry.invalidate(id);
|
|
4553
|
-
} catch {
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
const updated = cleanProjectRow(await findOne(ENV, { id }));
|
|
4557
|
-
return { handled: true, response: this.success({ project: updated }) };
|
|
4558
|
-
}
|
|
4559
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "retry" && m === "POST") {
|
|
4560
|
-
const id = decodeURIComponent(parts[1]);
|
|
4561
|
-
const envRow = await findOne(ENV, { id });
|
|
4562
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4563
|
-
if (envRow.status !== "failed" && envRow.status !== "provisioning") {
|
|
4564
|
-
return {
|
|
4565
|
-
handled: true,
|
|
4566
|
-
response: this.error(
|
|
4567
|
-
`Project '${id}' is '${envRow.status}'; only failed or provisioning projects can be retried.`,
|
|
4568
|
-
409
|
|
4569
|
-
)
|
|
4570
|
-
};
|
|
4571
|
-
}
|
|
4572
|
-
const driverName = envRow.database_driver;
|
|
4573
|
-
const resolved = resolveDriver(driverName);
|
|
4574
|
-
if (!resolved) {
|
|
4575
|
-
return {
|
|
4576
|
-
handled: true,
|
|
4577
|
-
response: this.error(
|
|
4578
|
-
`Driver '${driverName}' is no longer registered; retry aborted.`,
|
|
4579
|
-
503
|
|
4580
|
-
)
|
|
4581
|
-
};
|
|
4582
|
-
}
|
|
4583
|
-
let metadata = {};
|
|
4584
|
-
if (envRow.metadata) {
|
|
4585
|
-
if (typeof envRow.metadata === "string") {
|
|
4586
|
-
try {
|
|
4587
|
-
metadata = JSON.parse(envRow.metadata);
|
|
4588
|
-
} catch {
|
|
4589
|
-
metadata = {};
|
|
4590
|
-
}
|
|
4591
|
-
} else if (typeof envRow.metadata === "object") {
|
|
4592
|
-
metadata = { ...envRow.metadata };
|
|
4593
|
-
}
|
|
4594
|
-
}
|
|
4595
|
-
delete metadata.provisioningError;
|
|
4596
|
-
const retryStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4597
|
-
await ql.update(
|
|
4598
|
-
ENV,
|
|
4599
|
-
{
|
|
4600
|
-
status: "provisioning",
|
|
4601
|
-
metadata: JSON.stringify(metadata),
|
|
4602
|
-
updated_at: retryStartedAt
|
|
4603
|
-
},
|
|
4604
|
-
{ where: { id } }
|
|
4605
|
-
);
|
|
4606
|
-
const simulateRetryFailure = Boolean(metadata.__simulateFailure);
|
|
4607
|
-
const simulateRetryDelay = Number(metadata.__simulateDelayMs ?? 1500);
|
|
4608
|
-
const runRetry = async () => {
|
|
4609
|
-
try {
|
|
4610
|
-
if (simulateRetryDelay > 0) {
|
|
4611
|
-
await new Promise((r) => setTimeout(r, simulateRetryDelay));
|
|
4612
|
-
}
|
|
4613
|
-
if (simulateRetryFailure) {
|
|
4614
|
-
throw new Error("Simulated provisioning failure (metadata.__simulateFailure=true)");
|
|
4615
|
-
}
|
|
4616
|
-
let databaseUrl;
|
|
4617
|
-
let retrySecret = `mock-token-${id}`;
|
|
4618
|
-
try {
|
|
4619
|
-
const adapter = await getRealAdapter(resolved.name);
|
|
4620
|
-
if (adapter) {
|
|
4621
|
-
const result = await adapter.createDatabase({
|
|
4622
|
-
environmentId: id,
|
|
4623
|
-
databaseName: `p-${id.replace(/-/g, "").slice(0, 24)}`,
|
|
4624
|
-
region: "us-east-1",
|
|
4625
|
-
storageLimitMb: envRow.storage_limit_mb ?? 1024
|
|
4626
|
-
});
|
|
4627
|
-
databaseUrl = result.databaseUrl;
|
|
4628
|
-
if (result.plaintextSecret) retrySecret = result.plaintextSecret;
|
|
4629
|
-
} else {
|
|
4630
|
-
databaseUrl = buildDatabaseUrl(resolved.name, id);
|
|
4631
|
-
}
|
|
4632
|
-
} catch (adapterErr) {
|
|
4633
|
-
throw adapterErr instanceof Error ? adapterErr : new Error(String(adapterErr));
|
|
4634
|
-
}
|
|
4635
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4636
|
-
await ql.update(
|
|
4637
|
-
ENV,
|
|
4638
|
-
{
|
|
4639
|
-
status: "active",
|
|
4640
|
-
database_url: databaseUrl,
|
|
4641
|
-
database_driver: resolved.name,
|
|
4642
|
-
provisioned_at: nowIso,
|
|
4643
|
-
updated_at: nowIso
|
|
4644
|
-
},
|
|
4645
|
-
{ where: { id } }
|
|
4646
|
-
);
|
|
4647
|
-
const existingCred = await findOne(CRED, { environment_id: id, status: "active" });
|
|
4648
|
-
if (!existingCred) {
|
|
4649
|
-
await ql.insert(CRED, {
|
|
4650
|
-
id: randomUUID(),
|
|
4651
|
-
environment_id: id,
|
|
4652
|
-
secret_ciphertext: retrySecret,
|
|
4653
|
-
encryption_key_id: "noop",
|
|
4654
|
-
authorization: "full_access",
|
|
4655
|
-
status: "active",
|
|
4656
|
-
created_at: nowIso,
|
|
4657
|
-
updated_at: nowIso
|
|
4658
|
-
});
|
|
4659
|
-
}
|
|
4660
|
-
} catch (err) {
|
|
4661
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4662
|
-
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4663
|
-
await ql.update(
|
|
4664
|
-
ENV,
|
|
4665
|
-
{
|
|
4666
|
-
status: "failed",
|
|
4667
|
-
metadata: JSON.stringify({
|
|
4668
|
-
...metadata,
|
|
4669
|
-
provisioningError: { message, failedAt }
|
|
4670
|
-
}),
|
|
4671
|
-
updated_at: failedAt
|
|
4672
|
-
},
|
|
4673
|
-
{ where: { id } }
|
|
4674
|
-
);
|
|
4675
|
-
}
|
|
4676
|
-
};
|
|
4677
|
-
void runRetry();
|
|
4678
|
-
const envAfter = cleanProjectRow(await findOne(ENV, { id }));
|
|
4679
|
-
const retryRes = this.success({ project: envAfter });
|
|
4680
|
-
retryRes.status = 202;
|
|
4681
|
-
return { handled: true, response: retryRes };
|
|
4682
|
-
}
|
|
4683
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "activate" && m === "POST") {
|
|
4684
|
-
const id = decodeURIComponent(parts[1]);
|
|
4685
|
-
const envRow = await findOne(ENV, { id });
|
|
4686
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4687
|
-
return { handled: true, response: this.success({ project: cleanProjectRow(envRow), sessionUpdated: false }) };
|
|
4688
|
-
}
|
|
4689
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "credentials" && parts[3] === "rotate" && m === "POST") {
|
|
4690
|
-
const id = decodeURIComponent(parts[1]);
|
|
4691
|
-
const plaintext = body?.plaintext;
|
|
4692
|
-
if (!plaintext || typeof plaintext !== "string") {
|
|
4693
|
-
return { handled: true, response: this.error("plaintext is required", 400) };
|
|
4694
|
-
}
|
|
4695
|
-
const envRow = await findOne(ENV, { id });
|
|
4696
|
-
if (!envRow) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4697
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4698
|
-
let existing = await ql.find(CRED, { where: { environment_id: id, status: "active" } });
|
|
4699
|
-
if (existing && existing.value) existing = existing.value;
|
|
4700
|
-
for (const row of Array.isArray(existing) ? existing : []) {
|
|
4701
|
-
await ql.update(CRED, {
|
|
4702
|
-
status: "revoked",
|
|
4703
|
-
revoked_at: nowIso,
|
|
4704
|
-
updated_at: nowIso
|
|
4705
|
-
}, { where: { id: row.id } });
|
|
4706
|
-
}
|
|
4707
|
-
const credentialId = randomUUID();
|
|
4708
|
-
await ql.insert(CRED, {
|
|
4709
|
-
id: credentialId,
|
|
4710
|
-
environment_id: id,
|
|
4711
|
-
secret_ciphertext: plaintext,
|
|
4712
|
-
encryption_key_id: "noop",
|
|
4713
|
-
authorization: "full_access",
|
|
4714
|
-
status: "active",
|
|
4715
|
-
created_at: nowIso,
|
|
4716
|
-
updated_at: nowIso
|
|
4717
|
-
});
|
|
4718
|
-
const credential = await findOne(CRED, { id: credentialId });
|
|
4719
|
-
const credMeta = credential ? {
|
|
4720
|
-
id: credential.id,
|
|
4721
|
-
status: credential.status,
|
|
4722
|
-
authorization: credential.authorization,
|
|
4723
|
-
activatedAt: credential.created_at
|
|
4724
|
-
} : void 0;
|
|
4725
|
-
return { handled: true, response: this.success({ credential: credMeta }) };
|
|
4726
|
-
}
|
|
4727
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "GET") {
|
|
4728
|
-
const id = decodeURIComponent(parts[1]);
|
|
4729
|
-
let rows = await ql.find(MEM, { where: { environment_id: id } });
|
|
4730
|
-
if (rows && rows.value) rows = rows.value;
|
|
4731
|
-
const members = Array.isArray(rows) ? rows : [];
|
|
4732
|
-
const userIds = Array.from(new Set(members.map((mem) => mem.user_id).filter(Boolean)));
|
|
4733
|
-
const userMap = /* @__PURE__ */ new Map();
|
|
4734
|
-
for (const uid of userIds) {
|
|
4735
|
-
let row = null;
|
|
4736
|
-
for (const tableName of ["sys_user", "user"]) {
|
|
4737
|
-
try {
|
|
4738
|
-
const u = await ql.findOne(tableName, { where: { id: uid } });
|
|
4739
|
-
row = u?.value ?? u;
|
|
4740
|
-
if (row) break;
|
|
4741
|
-
} catch {
|
|
4742
|
-
}
|
|
4743
|
-
}
|
|
4744
|
-
if (row) userMap.set(String(uid), {
|
|
4745
|
-
id: row.id,
|
|
4746
|
-
name: row.name ?? row.display_name,
|
|
4747
|
-
email: row.email,
|
|
4748
|
-
image: row.image ?? row.avatar_url
|
|
4749
|
-
});
|
|
4750
|
-
}
|
|
4751
|
-
const enriched = members.map((mem) => ({
|
|
4752
|
-
...mem,
|
|
4753
|
-
user: userMap.get(String(mem.user_id)) ?? void 0
|
|
4754
|
-
}));
|
|
4755
|
-
return { handled: true, response: this.success({ members: enriched }) };
|
|
4756
|
-
}
|
|
4757
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "members" && m === "POST") {
|
|
4758
|
-
const id = decodeURIComponent(parts[1]);
|
|
4759
|
-
const project = await findOne(ENV, { id });
|
|
4760
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4761
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
4762
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
4763
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
4764
|
-
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
4765
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
4766
|
-
}
|
|
4767
|
-
const email = typeof body?.email === "string" ? String(body.email).trim().toLowerCase() : null;
|
|
4768
|
-
let inviteUserId = typeof body?.user_id === "string" ? String(body.user_id).trim() : null;
|
|
4769
|
-
let role = String(body?.role ?? "member").trim().toLowerCase();
|
|
4770
|
-
if (!["owner", "admin", "member", "viewer"].includes(role)) {
|
|
4771
|
-
return { handled: true, response: this.error(`Invalid role '${role}' (expected owner | admin | member | viewer)`, 400) };
|
|
4772
|
-
}
|
|
4773
|
-
if (!email && !inviteUserId) {
|
|
4774
|
-
return { handled: true, response: this.error("email or user_id is required", 400) };
|
|
4775
|
-
}
|
|
4776
|
-
if (!inviteUserId && email) {
|
|
4777
|
-
let row = null;
|
|
4778
|
-
for (const tableName of ["sys_user", "user"]) {
|
|
4779
|
-
try {
|
|
4780
|
-
const u = await ql.findOne(tableName, { where: { email } });
|
|
4781
|
-
row = u?.value ?? u;
|
|
4782
|
-
if (row) break;
|
|
4783
|
-
} catch {
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
if (!row?.id) {
|
|
4787
|
-
return { handled: true, response: this.error(`No user found with email '${email}'`, 404) };
|
|
4788
|
-
}
|
|
4789
|
-
inviteUserId = String(row.id);
|
|
4790
|
-
}
|
|
4791
|
-
const existing = await findOne(MEM, { environment_id: id, user_id: inviteUserId });
|
|
4792
|
-
if (existing) {
|
|
4793
|
-
return { handled: true, response: this.success({ member: existing, alreadyMember: true }) };
|
|
4794
|
-
}
|
|
4795
|
-
try {
|
|
4796
|
-
const memberId = randomUUID();
|
|
4797
|
-
await ql.insert(MEM, {
|
|
4798
|
-
id: memberId,
|
|
4799
|
-
environment_id: id,
|
|
4800
|
-
user_id: inviteUserId,
|
|
4801
|
-
role,
|
|
4802
|
-
invited_by: callerId,
|
|
4803
|
-
organization_id: project.organization_id ?? null
|
|
4804
|
-
});
|
|
4805
|
-
const created = await findOne(MEM, { id: memberId });
|
|
4806
|
-
return { handled: true, response: this.success({ member: created, alreadyMember: false }) };
|
|
4807
|
-
} catch (e) {
|
|
4808
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to add member", 500) };
|
|
4809
|
-
}
|
|
4810
|
-
}
|
|
4811
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "PATCH") {
|
|
4812
|
-
const id = decodeURIComponent(parts[1]);
|
|
4813
|
-
const memberId = decodeURIComponent(parts[3]);
|
|
4814
|
-
const project = await findOne(ENV, { id });
|
|
4815
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4816
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
4817
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
4818
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
4819
|
-
if (!callerMem || !["owner", "admin"].includes(String(callerMem.role))) {
|
|
4820
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
4821
|
-
}
|
|
4822
|
-
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
4823
|
-
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
4824
|
-
const newRole = String(body?.role ?? "").trim().toLowerCase();
|
|
4825
|
-
if (!["owner", "admin", "member", "viewer"].includes(newRole)) {
|
|
4826
|
-
return { handled: true, response: this.error(`Invalid role '${newRole}'`, 400) };
|
|
4827
|
-
}
|
|
4828
|
-
if (target.role === "owner" && newRole !== "owner") {
|
|
4829
|
-
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
4830
|
-
if (owners && owners.value) owners = owners.value;
|
|
4831
|
-
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
4832
|
-
if (ownerCount <= 1) {
|
|
4833
|
-
return { handled: true, response: this.error("Cannot demote the last owner", 409) };
|
|
4834
|
-
}
|
|
4835
|
-
}
|
|
4836
|
-
try {
|
|
4837
|
-
await ql.update(MEM, { role: newRole, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, { where: { id: memberId } });
|
|
4838
|
-
const updated = await findOne(MEM, { id: memberId });
|
|
4839
|
-
return { handled: true, response: this.success({ member: updated }) };
|
|
4840
|
-
} catch (e) {
|
|
4841
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to update role", 500) };
|
|
4842
|
-
}
|
|
4843
|
-
}
|
|
4844
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "members" && m === "DELETE") {
|
|
4845
|
-
const id = decodeURIComponent(parts[1]);
|
|
4846
|
-
const memberId = decodeURIComponent(parts[3]);
|
|
4847
|
-
const project = await findOne(ENV, { id });
|
|
4848
|
-
if (!project) return { handled: true, response: this.error(`Project '${id}' not found`, 404) };
|
|
4849
|
-
const callerId = await this.resolveCallerUserId(_context);
|
|
4850
|
-
if (!callerId) return { handled: true, response: this.error("Authentication required", 401) };
|
|
4851
|
-
const target = await findOne(MEM, { id: memberId, environment_id: id });
|
|
4852
|
-
if (!target) return { handled: true, response: this.error(`Member '${memberId}' not found`, 404) };
|
|
4853
|
-
const callerMem = await findOne(MEM, { environment_id: id, user_id: callerId });
|
|
4854
|
-
const isSelf = String(target.user_id) === String(callerId);
|
|
4855
|
-
const isPrivileged = callerMem && ["owner", "admin"].includes(String(callerMem.role));
|
|
4856
|
-
if (!isSelf && !isPrivileged) {
|
|
4857
|
-
return { handled: true, response: this.error("Forbidden \u2014 owner or admin required", 403) };
|
|
4858
|
-
}
|
|
4859
|
-
if (target.role === "owner") {
|
|
4860
|
-
let owners = await ql.find(MEM, { where: { environment_id: id, role: "owner" } });
|
|
4861
|
-
if (owners && owners.value) owners = owners.value;
|
|
4862
|
-
const ownerCount = Array.isArray(owners) ? owners.length : 0;
|
|
4863
|
-
if (ownerCount <= 1) {
|
|
4864
|
-
return { handled: true, response: this.error("Cannot remove the last owner", 409) };
|
|
4865
|
-
}
|
|
4866
|
-
}
|
|
4867
|
-
try {
|
|
4868
|
-
await ql.delete(MEM, { where: { id: memberId } });
|
|
4869
|
-
return { handled: true, response: this.success({ removed: true, memberId }) };
|
|
4870
|
-
} catch (e) {
|
|
4871
|
-
return { handled: true, response: this.error(e?.message ?? "Failed to remove member", 500) };
|
|
4872
|
-
}
|
|
4873
|
-
}
|
|
4874
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
4875
|
-
const envId = decodeURIComponent(parts[1]);
|
|
4876
|
-
let rows = await ql.find(PKG_INSTALL, { where: { environment_id: envId } });
|
|
4877
|
-
if (rows && rows.value) rows = rows.value;
|
|
4878
|
-
const installs = Array.isArray(rows) ? rows : [];
|
|
4879
|
-
const packages = await Promise.all(
|
|
4880
|
-
installs.map(async (r) => {
|
|
4881
|
-
let manifestId = null;
|
|
4882
|
-
let versionStr = null;
|
|
4883
|
-
try {
|
|
4884
|
-
if (r.package_id) {
|
|
4885
|
-
const pkg = await ql.findOne(PKG, { where: { id: r.package_id } });
|
|
4886
|
-
manifestId = pkg?.manifest_id ?? null;
|
|
4887
|
-
}
|
|
4888
|
-
if (r.package_version_id) {
|
|
4889
|
-
const ver = await ql.findOne(PKG_VERSION, { where: { id: r.package_version_id } });
|
|
4890
|
-
versionStr = ver?.version ?? null;
|
|
4891
|
-
}
|
|
4892
|
-
} catch {
|
|
4893
|
-
}
|
|
4894
|
-
return {
|
|
4895
|
-
...r,
|
|
4896
|
-
// Surface user-facing identifiers expected by client SDK
|
|
4897
|
-
packageId: manifestId,
|
|
4898
|
-
package_id: manifestId ?? r.package_id,
|
|
4899
|
-
version: versionStr ?? r.version ?? null
|
|
4900
|
-
};
|
|
4901
|
-
})
|
|
4902
|
-
);
|
|
4903
|
-
return { handled: true, response: this.success({ packages, total: packages.length }) };
|
|
4904
|
-
}
|
|
4905
|
-
if (parts.length === 3 && parts[0] === "projects" && parts[2] === "packages" && m === "POST") {
|
|
4906
|
-
const envId = decodeURIComponent(parts[1]);
|
|
4907
|
-
const { packageId, version, settings, enableOnInstall } = body ?? {};
|
|
4908
|
-
if (!packageId) return { handled: true, response: this.error("packageId is required", 400) };
|
|
4909
|
-
const qlSvc = await this.getObjectQLService();
|
|
4910
|
-
const pkgRegistry = qlSvc?.registry;
|
|
4911
|
-
const allPkgs = pkgRegistry?.getAllPackages?.() ?? [];
|
|
4912
|
-
const manifestEntry = allPkgs.find((p) => (p?.manifest?.id ?? p?.id) === packageId);
|
|
4913
|
-
const manifest = manifestEntry?.manifest ?? manifestEntry;
|
|
4914
|
-
if (!manifest) {
|
|
4915
|
-
return { handled: true, response: this.error(`Package '${packageId}' is not registered on this server`, 404) };
|
|
4916
|
-
}
|
|
4917
|
-
const CLOUD_SCOPES = /* @__PURE__ */ new Set(["cloud", "system", "platform"]);
|
|
4918
|
-
if (CLOUD_SCOPES.has(manifest?.scope)) {
|
|
4919
|
-
return { handled: true, response: this.error(`Package '${packageId}' has scope=${manifest.scope} and cannot be installed per-project`, 403) };
|
|
4920
|
-
}
|
|
4921
|
-
const projectRow = await findOne(ENV, { id: envId });
|
|
4922
|
-
if (!projectRow) {
|
|
4923
|
-
return { handled: true, response: this.error(`Project '${envId}' not found`, 404) };
|
|
4924
|
-
}
|
|
4925
|
-
const ownerOrgId = projectRow.organization_id ?? "system";
|
|
4926
|
-
let userId = "system";
|
|
4927
|
-
try {
|
|
4928
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
4929
|
-
const sessionData = await authService?.api?.getSession?.({
|
|
4930
|
-
headers: _context?.request?.headers
|
|
4931
|
-
});
|
|
4932
|
-
userId = sessionData?.user?.id ?? sessionData?.session?.userId ?? "system";
|
|
4933
|
-
} catch {
|
|
4934
|
-
}
|
|
4935
|
-
const resolvedVersion = version ?? manifest?.version ?? "1.0.0";
|
|
4936
|
-
const dup = await ql.findOne(PKG_INSTALL, {
|
|
4937
|
-
where: { environment_id: envId, package_id: packageId }
|
|
4938
|
-
});
|
|
4939
|
-
if (dup?.id) {
|
|
4940
|
-
return { handled: true, response: this.error(`Package '${packageId}' is already installed in this project`, 409) };
|
|
4941
|
-
}
|
|
4942
|
-
const sysPackageId = await ensureSysPackage(packageId, ownerOrgId, userId, manifest);
|
|
4943
|
-
const sysPackageVersionId = await ensureSysPackageVersion(sysPackageId, resolvedVersion, userId, manifest);
|
|
4944
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4945
|
-
const recordId = randomUUID();
|
|
4946
|
-
await ql.insert(PKG_INSTALL, {
|
|
4947
|
-
id: recordId,
|
|
4948
|
-
environment_id: envId,
|
|
4949
|
-
package_id: sysPackageId,
|
|
4950
|
-
package_version_id: sysPackageVersionId,
|
|
4951
|
-
status: "installed",
|
|
4952
|
-
enabled: enableOnInstall !== false,
|
|
4953
|
-
installed_at: nowIso,
|
|
4954
|
-
installed_by: userId,
|
|
4955
|
-
updated_at: nowIso,
|
|
4956
|
-
settings: settings ? JSON.stringify(settings) : null
|
|
4957
|
-
});
|
|
4958
|
-
const record = await ql.findOne(PKG_INSTALL, { where: { id: recordId } });
|
|
4959
|
-
try {
|
|
4960
|
-
await this.kernelManager?.evict(envId);
|
|
4961
|
-
} catch {
|
|
4962
|
-
}
|
|
4963
|
-
return { handled: true, response: this.success({ package: record }) };
|
|
4964
|
-
}
|
|
4965
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "packages" && m === "GET") {
|
|
4966
|
-
const envId = decodeURIComponent(parts[1]);
|
|
4967
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
4968
|
-
const record = await ql.findOne(PKG_INSTALL, { where: { environment_id: envId, package_id: pkgId } });
|
|
4969
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
4970
|
-
return { handled: true, response: this.success({ package: record }) };
|
|
4971
|
-
}
|
|
4972
|
-
if (parts.length === 4 && parts[0] === "projects" && parts[2] === "packages" && m === "DELETE") {
|
|
4973
|
-
const envId = decodeURIComponent(parts[1]);
|
|
4974
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
4975
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
4976
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
4977
|
-
const allPkgs0 = this.kernel.packages?.getAll?.() ?? [];
|
|
4978
|
-
const m0 = allPkgs0.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
4979
|
-
if (m0?.scope && ["cloud", "system", "platform"].includes(m0.scope)) {
|
|
4980
|
-
return { handled: true, response: this.error(`Package '${pkgId}' with scope=${m0.scope} cannot be uninstalled`, 403) };
|
|
4981
|
-
}
|
|
4982
|
-
await ql.delete(PKG_INSTALL, { where: { id: record.id } });
|
|
4983
|
-
try {
|
|
4984
|
-
await this.kernelManager?.evict(envId);
|
|
4985
|
-
} catch {
|
|
4986
|
-
}
|
|
4987
|
-
return { handled: true, response: this.success({ id: record.id, success: true }) };
|
|
4988
|
-
}
|
|
4989
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "enable" && m === "PATCH") {
|
|
4990
|
-
const envId = decodeURIComponent(parts[1]);
|
|
4991
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
4992
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
4993
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
4994
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4995
|
-
await ql.update(PKG_INSTALL, { enabled: true, status: "installed", updated_at: nowIso }, { where: { id: record.id } });
|
|
4996
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
4997
|
-
try {
|
|
4998
|
-
await this.kernelManager?.evict(envId);
|
|
4999
|
-
} catch {
|
|
5000
|
-
}
|
|
5001
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5002
|
-
}
|
|
5003
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "disable" && m === "PATCH") {
|
|
5004
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5005
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5006
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5007
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5008
|
-
const allPkgs1 = this.kernel.packages?.getAll?.() ?? [];
|
|
5009
|
-
const m1 = allPkgs1.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
5010
|
-
if (m1?.scope && ["cloud", "system", "platform"].includes(m1.scope)) {
|
|
5011
|
-
return { handled: true, response: this.error(`Package '${pkgId}' with scope=${m1.scope} cannot be disabled`, 403) };
|
|
5012
|
-
}
|
|
5013
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5014
|
-
await ql.update(PKG_INSTALL, { enabled: false, status: "disabled", updated_at: nowIso }, { where: { id: record.id } });
|
|
5015
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
5016
|
-
try {
|
|
5017
|
-
await this.kernelManager?.evict(envId);
|
|
5018
|
-
} catch {
|
|
5019
|
-
}
|
|
5020
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5021
|
-
}
|
|
5022
|
-
if (parts.length === 5 && parts[0] === "projects" && parts[2] === "packages" && parts[4] === "upgrade" && m === "POST") {
|
|
5023
|
-
const envId = decodeURIComponent(parts[1]);
|
|
5024
|
-
const pkgId = decodeURIComponent(parts[3]);
|
|
5025
|
-
const record = await findInstallByManifestId(envId, pkgId);
|
|
5026
|
-
if (!record) return { handled: true, response: this.error(`Package '${pkgId}' is not installed in this project`, 404) };
|
|
5027
|
-
const { targetVersion } = body ?? {};
|
|
5028
|
-
const allPkgs2 = this.kernel.packages?.getAll?.() ?? [];
|
|
5029
|
-
const manifest2 = allPkgs2.find((p) => (p.manifest?.id ?? p.id) === pkgId)?.manifest;
|
|
5030
|
-
const currentVer = await ql.findOne(PKG_VERSION, { where: { id: record.package_version_id } });
|
|
5031
|
-
const newVersion = targetVersion ?? manifest2?.version ?? currentVer?.version ?? "1.0.0";
|
|
5032
|
-
if (newVersion === currentVer?.version) {
|
|
5033
|
-
return { handled: true, response: this.success({ package: record, message: "Already at target version" }) };
|
|
5034
|
-
}
|
|
5035
|
-
let userId = "system";
|
|
5036
|
-
try {
|
|
5037
|
-
const authService = await this.getService(import_system.CoreServiceName.enum.auth);
|
|
5038
|
-
const sessionData = await authService?.api?.getSession?.({
|
|
5039
|
-
headers: _context?.request?.headers
|
|
5040
|
-
});
|
|
5041
|
-
userId = sessionData?.user?.id ?? "system";
|
|
5042
|
-
} catch {
|
|
5043
|
-
}
|
|
5044
|
-
const newVersionId = await ensureSysPackageVersion(record.package_id, newVersion, userId, manifest2);
|
|
5045
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5046
|
-
await ql.update(PKG_INSTALL, {
|
|
5047
|
-
package_version_id: newVersionId,
|
|
5048
|
-
status: "installed",
|
|
5049
|
-
updated_at: nowIso
|
|
5050
|
-
}, { where: { id: record.id } });
|
|
5051
|
-
const updated = await ql.findOne(PKG_INSTALL, { where: { id: record.id } });
|
|
5052
|
-
try {
|
|
5053
|
-
await this.kernelManager?.evict(envId);
|
|
5054
|
-
} catch {
|
|
5055
|
-
}
|
|
5056
|
-
return { handled: true, response: this.success({ package: updated }) };
|
|
5057
|
-
}
|
|
5058
|
-
} catch (e) {
|
|
5059
|
-
return { handled: true, response: this.error(e.message, e.statusCode || 500) };
|
|
5060
|
-
}
|
|
5061
|
-
return { handled: false };
|
|
5062
|
-
}
|
|
5063
|
-
/**
|
|
5064
|
-
* Cascade-delete a project: cred / member / package_installation rows,
|
|
5065
|
-
* then the physical database via the provisioning adapter, then the
|
|
5066
|
-
* `sys_environment` row itself. Used by both `DELETE /cloud/environments/:id`
|
|
5067
|
-
* and the org-cascade in `DELETE /cloud/organizations/:id`.
|
|
5068
|
-
*
|
|
5069
|
-
* Idempotent and best-effort: missing rows / unreachable adapters
|
|
5070
|
-
* become warnings rather than hard failures, so a half-provisioned
|
|
5071
|
-
* project can still be cleaned out.
|
|
4038
|
+
* Driver binding
|
|
4039
|
+
* --------------
|
|
4040
|
+
* Environments are not tied to any specific driver. At provisioning time the
|
|
4041
|
+
* caller passes `driver` (a short name such as `memory`, `turso`, or any
|
|
4042
|
+
* future `sql` / `postgres` driver). The dispatcher validates the name
|
|
4043
|
+
* against the kernel's registered driver services (`driver.<name>`) and
|
|
4044
|
+
* derives an appropriate placeholder `database_url` for the chosen driver.
|
|
4045
|
+
* If `driver` is omitted, the dispatcher auto-selects the first available
|
|
4046
|
+
* in preference order: turso → memory → any other registered driver.
|
|
4047
|
+
*
|
|
4048
|
+
* Backed by ObjectQL sys_environment / sys_environment_credential /
|
|
4049
|
+
* sys_environment_member tables (registered by
|
|
4050
|
+
* `@objectstack/service-tenant`'s `createTenantPlugin`).
|
|
4051
|
+
* Physical database addressing (database_url, database_driver, etc.)
|
|
4052
|
+
* is stored directly on the sys_environment row.
|
|
5072
4053
|
*/
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
error: `Project '${environmentId}' is the default project for its organization. Pass ?force=1 to delete it.`,
|
|
5089
|
-
warnings
|
|
5090
|
-
};
|
|
5091
|
-
}
|
|
5092
|
-
const cascade = [
|
|
5093
|
-
{ object: "sys_environment_credential", field: "environment_id" },
|
|
5094
|
-
{ object: "sys_environment_member", field: "environment_id" },
|
|
5095
|
-
{ object: "sys_package_installation", field: "environment_id" }
|
|
5096
|
-
];
|
|
5097
|
-
for (const { object, field } of cascade) {
|
|
5098
|
-
try {
|
|
5099
|
-
let rows = await ql.find(object, { where: { [field]: environmentId } });
|
|
5100
|
-
if (rows && rows.value) rows = rows.value;
|
|
5101
|
-
if (Array.isArray(rows)) {
|
|
5102
|
-
for (const r of rows) {
|
|
5103
|
-
if (r?.id != null) {
|
|
5104
|
-
try {
|
|
5105
|
-
await ql.delete(object, { where: { id: r.id } });
|
|
5106
|
-
} catch (innerErr) {
|
|
5107
|
-
warnings.push(
|
|
5108
|
-
`Failed to delete ${object} ${r.id}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
5109
|
-
);
|
|
5110
|
-
}
|
|
5111
|
-
}
|
|
4054
|
+
/**
|
|
4055
|
+
* Resolve the calling user id from the request session, if any.
|
|
4056
|
+
* Returns `undefined` for anonymous calls or when auth is not wired up.
|
|
4057
|
+
*/
|
|
4058
|
+
async resolveActiveOrganizationId(context) {
|
|
4059
|
+
try {
|
|
4060
|
+
const authService = await this.resolveService(import_system2.CoreServiceName.enum.auth);
|
|
4061
|
+
const rawHeaders = context.request?.headers;
|
|
4062
|
+
let headers = rawHeaders;
|
|
4063
|
+
if (rawHeaders && typeof rawHeaders === "object" && typeof rawHeaders.get !== "function") {
|
|
4064
|
+
try {
|
|
4065
|
+
const h = new Headers();
|
|
4066
|
+
for (const [k, v] of Object.entries(rawHeaders)) {
|
|
4067
|
+
if (v == null) continue;
|
|
4068
|
+
h.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
5112
4069
|
}
|
|
4070
|
+
headers = h;
|
|
4071
|
+
} catch {
|
|
4072
|
+
headers = rawHeaders;
|
|
5113
4073
|
}
|
|
5114
|
-
} catch (err) {
|
|
5115
|
-
warnings.push(
|
|
5116
|
-
`Failed to enumerate ${object} for project ${environmentId}: ${err instanceof Error ? err.message : String(err)}`
|
|
5117
|
-
);
|
|
5118
|
-
}
|
|
5119
|
-
}
|
|
5120
|
-
const driver = row.database_driver ?? "memory";
|
|
5121
|
-
const databaseUrl = row.database_url;
|
|
5122
|
-
const databaseName = `p-${String(environmentId).replace(/-/g, "").slice(0, 24)}`;
|
|
5123
|
-
try {
|
|
5124
|
-
const adapter = await getRealAdapter(driver);
|
|
5125
|
-
if (adapter?.deleteDatabase) {
|
|
5126
|
-
await adapter.deleteDatabase({ environmentId, databaseName, databaseUrl });
|
|
5127
|
-
} else {
|
|
5128
|
-
warnings.push(`No adapter for driver '${driver}'; physical DB for project ${environmentId} not released.`);
|
|
5129
|
-
}
|
|
5130
|
-
} catch (err) {
|
|
5131
|
-
warnings.push(
|
|
5132
|
-
`Failed to delete physical database for project ${environmentId}: ${err instanceof Error ? err.message : String(err)}`
|
|
5133
|
-
);
|
|
5134
|
-
}
|
|
5135
|
-
try {
|
|
5136
|
-
await ql.delete(ENV, { where: { id: environmentId } });
|
|
5137
|
-
} catch (err) {
|
|
5138
|
-
return {
|
|
5139
|
-
ok: false,
|
|
5140
|
-
status: 500,
|
|
5141
|
-
error: `Failed to delete sys_environment row: ${err instanceof Error ? err.message : String(err)}`,
|
|
5142
|
-
warnings
|
|
5143
|
-
};
|
|
5144
|
-
}
|
|
5145
|
-
if (this.envRegistry?.invalidate) {
|
|
5146
|
-
try {
|
|
5147
|
-
await this.envRegistry.invalidate(environmentId);
|
|
5148
|
-
} catch {
|
|
5149
4074
|
}
|
|
4075
|
+
const apiObj = authService?.auth?.api ?? authService?.api;
|
|
4076
|
+
const sessionData = await apiObj?.getSession?.call(apiObj, { headers });
|
|
4077
|
+
const oid = sessionData?.session?.activeOrganizationId;
|
|
4078
|
+
return typeof oid === "string" && oid.length > 0 ? oid : void 0;
|
|
4079
|
+
} catch {
|
|
4080
|
+
return void 0;
|
|
5150
4081
|
}
|
|
5151
|
-
return { ok: true, warnings };
|
|
5152
4082
|
}
|
|
5153
4083
|
/**
|
|
5154
4084
|
* Handles Storage requests
|
|
5155
4085
|
* path: sub-path after /storage/
|
|
5156
4086
|
*/
|
|
5157
4087
|
async handleStorage(path, method, file, context) {
|
|
5158
|
-
const storageService = await this.getService(
|
|
4088
|
+
const storageService = await this.getService(import_system2.CoreServiceName.enum["file-storage"]) || this.kernel.services?.["file-storage"];
|
|
5159
4089
|
if (!storageService) {
|
|
5160
4090
|
return { handled: true, response: this.error("File storage not configured", 501) };
|
|
5161
4091
|
}
|
|
@@ -5220,6 +4150,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5220
4150
|
*
|
|
5221
4151
|
* Routes:
|
|
5222
4152
|
* GET / → listFlows
|
|
4153
|
+
* GET /actions → getActionDescriptors (ADR-0018; ?paradigm/?source/?category filters)
|
|
4154
|
+
* GET /connectors → getConnectorDescriptors (ADR-0022; ?type filter)
|
|
5223
4155
|
* GET /:name → getFlow
|
|
5224
4156
|
* POST / → createFlow (registerFlow)
|
|
5225
4157
|
* PUT /:name → updateFlow
|
|
@@ -5228,9 +4160,11 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5228
4160
|
* POST /:name/toggle → toggleFlow
|
|
5229
4161
|
* GET /:name/runs → listRuns
|
|
5230
4162
|
* GET /:name/runs/:runId → getRun
|
|
4163
|
+
* POST /:name/runs/:runId/resume → resume a paused run (screen input / ADR-0019)
|
|
4164
|
+
* GET /:name/runs/:runId/screen → the screen a paused run awaits
|
|
5231
4165
|
*/
|
|
5232
4166
|
async handleAutomation(path, method, body, context, query) {
|
|
5233
|
-
const automationService = await this.getService(
|
|
4167
|
+
const automationService = await this.getService(import_system2.CoreServiceName.enum.automation);
|
|
5234
4168
|
if (!automationService) return { handled: false };
|
|
5235
4169
|
const m = method.toUpperCase();
|
|
5236
4170
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
@@ -5257,6 +4191,32 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5257
4191
|
return { handled: true, response: this.success(body) };
|
|
5258
4192
|
}
|
|
5259
4193
|
}
|
|
4194
|
+
if (parts[0] === "actions" && parts.length === 1 && m === "GET") {
|
|
4195
|
+
if (typeof automationService.getActionDescriptors === "function") {
|
|
4196
|
+
let actions = automationService.getActionDescriptors() ?? [];
|
|
4197
|
+
if (query?.paradigm) {
|
|
4198
|
+
actions = actions.filter((a) => Array.isArray(a?.paradigms) && a.paradigms.includes(query.paradigm));
|
|
4199
|
+
}
|
|
4200
|
+
if (query?.source) {
|
|
4201
|
+
actions = actions.filter((a) => a?.source === query.source);
|
|
4202
|
+
}
|
|
4203
|
+
if (query?.category) {
|
|
4204
|
+
actions = actions.filter((a) => a?.category === query.category);
|
|
4205
|
+
}
|
|
4206
|
+
return { handled: true, response: this.success({ actions, total: actions.length }) };
|
|
4207
|
+
}
|
|
4208
|
+
return { handled: true, response: this.success({ actions: [], total: 0 }) };
|
|
4209
|
+
}
|
|
4210
|
+
if (parts[0] === "connectors" && parts.length === 1 && m === "GET") {
|
|
4211
|
+
if (typeof automationService.getConnectorDescriptors === "function") {
|
|
4212
|
+
let connectors = automationService.getConnectorDescriptors() ?? [];
|
|
4213
|
+
if (query?.type) {
|
|
4214
|
+
connectors = connectors.filter((c) => c?.type === query.type);
|
|
4215
|
+
}
|
|
4216
|
+
return { handled: true, response: this.success({ connectors, total: connectors.length }) };
|
|
4217
|
+
}
|
|
4218
|
+
return { handled: true, response: this.success({ connectors: [], total: 0 }) };
|
|
4219
|
+
}
|
|
5260
4220
|
if (parts.length >= 1) {
|
|
5261
4221
|
const name = parts[0];
|
|
5262
4222
|
if (parts[1] === "trigger" && m === "POST") {
|
|
@@ -5296,7 +4256,28 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5296
4256
|
return { handled: true, response: this.success({ name, enabled: body?.enabled ?? true }) };
|
|
5297
4257
|
}
|
|
5298
4258
|
}
|
|
5299
|
-
if (parts[1] === "runs" && parts[2] && m === "
|
|
4259
|
+
if (parts[1] === "runs" && parts[2] && parts[3] === "resume" && m === "POST") {
|
|
4260
|
+
if (typeof automationService.resume === "function") {
|
|
4261
|
+
const b = body && typeof body === "object" ? body : {};
|
|
4262
|
+
const inputs = b.inputs ?? b.variables;
|
|
4263
|
+
const signal = {};
|
|
4264
|
+
if (inputs && typeof inputs === "object") signal.variables = inputs;
|
|
4265
|
+
if (b.output && typeof b.output === "object") signal.output = b.output;
|
|
4266
|
+
if (typeof b.branchLabel === "string") signal.branchLabel = b.branchLabel;
|
|
4267
|
+
const result = await automationService.resume(parts[2], signal);
|
|
4268
|
+
return { handled: true, response: this.success(result) };
|
|
4269
|
+
}
|
|
4270
|
+
return { handled: true, response: this.error("Resume not supported", 501) };
|
|
4271
|
+
}
|
|
4272
|
+
if (parts[1] === "runs" && parts[2] && parts[3] === "screen" && m === "GET") {
|
|
4273
|
+
if (typeof automationService.getSuspendedScreen === "function") {
|
|
4274
|
+
const screen = automationService.getSuspendedScreen(parts[2]);
|
|
4275
|
+
if (!screen) return { handled: true, response: this.error("No pending screen for run", 404) };
|
|
4276
|
+
return { handled: true, response: this.success({ runId: parts[2], screen }) };
|
|
4277
|
+
}
|
|
4278
|
+
return { handled: true, response: this.error("Screen lookup not supported", 501) };
|
|
4279
|
+
}
|
|
4280
|
+
if (parts[1] === "runs" && parts[2] && !parts[3] && m === "GET") {
|
|
5300
4281
|
if (typeof automationService.getRun === "function") {
|
|
5301
4282
|
const run = await automationService.getRun(parts[2]);
|
|
5302
4283
|
if (!run) return { handled: true, response: this.error("Execution not found", 404) };
|
|
@@ -5622,11 +4603,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5622
4603
|
if (forbidden) {
|
|
5623
4604
|
return { handled: true, response: forbidden };
|
|
5624
4605
|
}
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
cleanPath = scopedMatch[1] ?? "";
|
|
5629
|
-
}
|
|
4606
|
+
const scopedMatch = cleanPath.match(/^\/projects\/[^/]+(\/.*)?$/);
|
|
4607
|
+
if (scopedMatch) {
|
|
4608
|
+
cleanPath = scopedMatch[1] ?? "";
|
|
5630
4609
|
}
|
|
5631
4610
|
try {
|
|
5632
4611
|
if ((cleanPath === "/discovery" || cleanPath === "") && method === "GET") {
|
|
@@ -5677,9 +4656,6 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
5677
4656
|
if (cleanPath.startsWith("/packages")) {
|
|
5678
4657
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
5679
4658
|
}
|
|
5680
|
-
if (cleanPath.startsWith("/cloud")) {
|
|
5681
|
-
return this.handleCloud(cleanPath.substring(6), method, body, query, context);
|
|
5682
|
-
}
|
|
5683
4659
|
if (cleanPath.startsWith("/i18n")) {
|
|
5684
4660
|
return this.handleI18n(cleanPath.substring(5), method, query, context);
|
|
5685
4661
|
}
|
|
@@ -6374,6 +5350,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6374
5350
|
errorResponse(err, res);
|
|
6375
5351
|
}
|
|
6376
5352
|
});
|
|
5353
|
+
server.get(`${prefix}/packages/:id/export`, async (req, res) => {
|
|
5354
|
+
try {
|
|
5355
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/export`, "GET", {}, req.query, { request: req });
|
|
5356
|
+
sendResult(result, res);
|
|
5357
|
+
} catch (err) {
|
|
5358
|
+
errorResponse(err, res);
|
|
5359
|
+
}
|
|
5360
|
+
});
|
|
6377
5361
|
server.get(`${prefix}/packages/:id`, async (req, res) => {
|
|
6378
5362
|
try {
|
|
6379
5363
|
const result = await dispatcher.handlePackages(`/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
@@ -6422,214 +5406,6 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6422
5406
|
errorResponse(err, res);
|
|
6423
5407
|
}
|
|
6424
5408
|
});
|
|
6425
|
-
server.get(`${prefix}/cloud/drivers`, async (req, res) => {
|
|
6426
|
-
try {
|
|
6427
|
-
const result = await dispatcher.handleCloud("/drivers", "GET", {}, req.query, { request: req });
|
|
6428
|
-
sendResult(result, res);
|
|
6429
|
-
} catch (err) {
|
|
6430
|
-
errorResponse(err, res);
|
|
6431
|
-
}
|
|
6432
|
-
});
|
|
6433
|
-
server.post(`${prefix}/cloud/admin/platform-sso/backfill`, async (req, res) => {
|
|
6434
|
-
try {
|
|
6435
|
-
const result = await dispatcher.handleCloud("/admin/platform-sso/backfill", "POST", req.body, req.query, { request: req });
|
|
6436
|
-
sendResult(result, res);
|
|
6437
|
-
} catch (err) {
|
|
6438
|
-
errorResponse(err, res);
|
|
6439
|
-
}
|
|
6440
|
-
});
|
|
6441
|
-
server.get(`${prefix}/cloud/templates`, async (req, res) => {
|
|
6442
|
-
try {
|
|
6443
|
-
const result = await dispatcher.handleCloud("/templates", "GET", {}, req.query, { request: req });
|
|
6444
|
-
sendResult(result, res);
|
|
6445
|
-
} catch (err) {
|
|
6446
|
-
errorResponse(err, res);
|
|
6447
|
-
}
|
|
6448
|
-
});
|
|
6449
|
-
server.get(`${prefix}/cloud/environments`, async (req, res) => {
|
|
6450
|
-
try {
|
|
6451
|
-
const result = await dispatcher.handleCloud("/projects", "GET", {}, req.query, { request: req });
|
|
6452
|
-
sendResult(result, res);
|
|
6453
|
-
} catch (err) {
|
|
6454
|
-
errorResponse(err, res);
|
|
6455
|
-
}
|
|
6456
|
-
});
|
|
6457
|
-
server.post(`${prefix}/cloud/environments`, async (req, res) => {
|
|
6458
|
-
try {
|
|
6459
|
-
const result = await dispatcher.handleCloud("/projects", "POST", req.body, {}, { request: req });
|
|
6460
|
-
sendResult(result, res);
|
|
6461
|
-
} catch (err) {
|
|
6462
|
-
errorResponse(err, res);
|
|
6463
|
-
}
|
|
6464
|
-
});
|
|
6465
|
-
server.get(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6466
|
-
try {
|
|
6467
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
6468
|
-
sendResult(result, res);
|
|
6469
|
-
} catch (err) {
|
|
6470
|
-
errorResponse(err, res);
|
|
6471
|
-
}
|
|
6472
|
-
});
|
|
6473
|
-
server.patch(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6474
|
-
try {
|
|
6475
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "PATCH", req.body, {}, { request: req });
|
|
6476
|
-
sendResult(result, res);
|
|
6477
|
-
} catch (err) {
|
|
6478
|
-
errorResponse(err, res);
|
|
6479
|
-
}
|
|
6480
|
-
});
|
|
6481
|
-
server.delete(`${prefix}/cloud/environments/:id`, async (req, res) => {
|
|
6482
|
-
try {
|
|
6483
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}`, "DELETE", {}, req.query, { request: req });
|
|
6484
|
-
sendResult(result, res);
|
|
6485
|
-
} catch (err) {
|
|
6486
|
-
errorResponse(err, res);
|
|
6487
|
-
}
|
|
6488
|
-
});
|
|
6489
|
-
server.delete(`${prefix}/cloud/organizations/:id`, async (req, res) => {
|
|
6490
|
-
try {
|
|
6491
|
-
const result = await dispatcher.handleCloud(`/organizations/${req.params.id}`, "DELETE", {}, req.query, { request: req });
|
|
6492
|
-
sendResult(result, res);
|
|
6493
|
-
} catch (err) {
|
|
6494
|
-
errorResponse(err, res);
|
|
6495
|
-
}
|
|
6496
|
-
});
|
|
6497
|
-
server.post(`${prefix}/cloud/environments/:id/hostname`, async (req, res) => {
|
|
6498
|
-
try {
|
|
6499
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/hostname`, "POST", req.body, {}, { request: req });
|
|
6500
|
-
sendResult(result, res);
|
|
6501
|
-
} catch (err) {
|
|
6502
|
-
errorResponse(err, res);
|
|
6503
|
-
}
|
|
6504
|
-
});
|
|
6505
|
-
server.put(`${prefix}/cloud/environments/:id/hostname`, async (req, res) => {
|
|
6506
|
-
try {
|
|
6507
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/hostname`, "PUT", req.body, {}, { request: req });
|
|
6508
|
-
sendResult(result, res);
|
|
6509
|
-
} catch (err) {
|
|
6510
|
-
errorResponse(err, res);
|
|
6511
|
-
}
|
|
6512
|
-
});
|
|
6513
|
-
server.post(`${prefix}/cloud/environments/:id/rotate-credential`, async (req, res) => {
|
|
6514
|
-
try {
|
|
6515
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/rotate-credential`, "POST", req.body, {}, { request: req });
|
|
6516
|
-
sendResult(result, res);
|
|
6517
|
-
} catch (err) {
|
|
6518
|
-
errorResponse(err, res);
|
|
6519
|
-
}
|
|
6520
|
-
});
|
|
6521
|
-
server.post(`${prefix}/cloud/environments/:id/credentials/rotate`, async (req, res) => {
|
|
6522
|
-
try {
|
|
6523
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/credentials/rotate`, "POST", req.body, {}, { request: req });
|
|
6524
|
-
sendResult(result, res);
|
|
6525
|
-
} catch (err) {
|
|
6526
|
-
errorResponse(err, res);
|
|
6527
|
-
}
|
|
6528
|
-
});
|
|
6529
|
-
server.post(`${prefix}/cloud/environments/:id/activate`, async (req, res) => {
|
|
6530
|
-
try {
|
|
6531
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/activate`, "POST", req.body, {}, { request: req });
|
|
6532
|
-
sendResult(result, res);
|
|
6533
|
-
} catch (err) {
|
|
6534
|
-
errorResponse(err, res);
|
|
6535
|
-
}
|
|
6536
|
-
});
|
|
6537
|
-
server.post(`${prefix}/cloud/environments/:id/retry`, async (req, res) => {
|
|
6538
|
-
try {
|
|
6539
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/retry`, "POST", req.body, {}, { request: req });
|
|
6540
|
-
sendResult(result, res);
|
|
6541
|
-
} catch (err) {
|
|
6542
|
-
errorResponse(err, res);
|
|
6543
|
-
}
|
|
6544
|
-
});
|
|
6545
|
-
server.get(`${prefix}/cloud/environments/:id/members`, async (req, res) => {
|
|
6546
|
-
try {
|
|
6547
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members`, "GET", {}, req.query, { request: req });
|
|
6548
|
-
sendResult(result, res);
|
|
6549
|
-
} catch (err) {
|
|
6550
|
-
errorResponse(err, res);
|
|
6551
|
-
}
|
|
6552
|
-
});
|
|
6553
|
-
server.post(`${prefix}/cloud/environments/:id/members`, async (req, res) => {
|
|
6554
|
-
try {
|
|
6555
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members`, "POST", req.body, {}, { request: req });
|
|
6556
|
-
sendResult(result, res);
|
|
6557
|
-
} catch (err) {
|
|
6558
|
-
errorResponse(err, res);
|
|
6559
|
-
}
|
|
6560
|
-
});
|
|
6561
|
-
server.patch(`${prefix}/cloud/environments/:id/members/:memberId`, async (req, res) => {
|
|
6562
|
-
try {
|
|
6563
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members/${req.params.memberId}`, "PATCH", req.body, {}, { request: req });
|
|
6564
|
-
sendResult(result, res);
|
|
6565
|
-
} catch (err) {
|
|
6566
|
-
errorResponse(err, res);
|
|
6567
|
-
}
|
|
6568
|
-
});
|
|
6569
|
-
server.delete(`${prefix}/cloud/environments/:id/members/:memberId`, async (req, res) => {
|
|
6570
|
-
try {
|
|
6571
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/members/${req.params.memberId}`, "DELETE", req.body ?? {}, {}, { request: req });
|
|
6572
|
-
sendResult(result, res);
|
|
6573
|
-
} catch (err) {
|
|
6574
|
-
errorResponse(err, res);
|
|
6575
|
-
}
|
|
6576
|
-
});
|
|
6577
|
-
server.get(`${prefix}/cloud/environments/:id/packages`, async (req, res) => {
|
|
6578
|
-
try {
|
|
6579
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages`, "GET", {}, req.query, { request: req });
|
|
6580
|
-
sendResult(result, res);
|
|
6581
|
-
} catch (err) {
|
|
6582
|
-
errorResponse(err, res);
|
|
6583
|
-
}
|
|
6584
|
-
});
|
|
6585
|
-
server.post(`${prefix}/cloud/environments/:id/packages`, async (req, res) => {
|
|
6586
|
-
try {
|
|
6587
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages`, "POST", req.body, {}, { request: req });
|
|
6588
|
-
sendResult(result, res);
|
|
6589
|
-
} catch (err) {
|
|
6590
|
-
errorResponse(err, res);
|
|
6591
|
-
}
|
|
6592
|
-
});
|
|
6593
|
-
server.get(`${prefix}/cloud/environments/:id/packages/:pkgId`, async (req, res) => {
|
|
6594
|
-
try {
|
|
6595
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages/${req.params.pkgId}`, "GET", {}, req.query, { request: req });
|
|
6596
|
-
sendResult(result, res);
|
|
6597
|
-
} catch (err) {
|
|
6598
|
-
errorResponse(err, res);
|
|
6599
|
-
}
|
|
6600
|
-
});
|
|
6601
|
-
server.delete(`${prefix}/cloud/environments/:id/packages/:pkgId`, async (req, res) => {
|
|
6602
|
-
try {
|
|
6603
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages/${req.params.pkgId}`, "DELETE", {}, {}, { request: req });
|
|
6604
|
-
sendResult(result, res);
|
|
6605
|
-
} catch (err) {
|
|
6606
|
-
errorResponse(err, res);
|
|
6607
|
-
}
|
|
6608
|
-
});
|
|
6609
|
-
server.patch(`${prefix}/cloud/environments/:id/packages/:pkgId/enable`, async (req, res) => {
|
|
6610
|
-
try {
|
|
6611
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages/${req.params.pkgId}/enable`, "PATCH", {}, {}, { request: req });
|
|
6612
|
-
sendResult(result, res);
|
|
6613
|
-
} catch (err) {
|
|
6614
|
-
errorResponse(err, res);
|
|
6615
|
-
}
|
|
6616
|
-
});
|
|
6617
|
-
server.patch(`${prefix}/cloud/environments/:id/packages/:pkgId/disable`, async (req, res) => {
|
|
6618
|
-
try {
|
|
6619
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages/${req.params.pkgId}/disable`, "PATCH", {}, {}, { request: req });
|
|
6620
|
-
sendResult(result, res);
|
|
6621
|
-
} catch (err) {
|
|
6622
|
-
errorResponse(err, res);
|
|
6623
|
-
}
|
|
6624
|
-
});
|
|
6625
|
-
server.post(`${prefix}/cloud/environments/:id/packages/:pkgId/upgrade`, async (req, res) => {
|
|
6626
|
-
try {
|
|
6627
|
-
const result = await dispatcher.handleCloud(`/projects/${req.params.id}/packages/${req.params.pkgId}/upgrade`, "POST", req.body, {}, { request: req });
|
|
6628
|
-
sendResult(result, res);
|
|
6629
|
-
} catch (err) {
|
|
6630
|
-
errorResponse(err, res);
|
|
6631
|
-
}
|
|
6632
|
-
});
|
|
6633
5409
|
server.post(`${prefix}/storage/upload`, async (req, res) => {
|
|
6634
5410
|
try {
|
|
6635
5411
|
const result = await dispatcher.handleStorage("upload", "POST", req.body, { request: req });
|
|
@@ -6721,31 +5497,47 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6721
5497
|
});
|
|
6722
5498
|
server.post(`${base}/automation/:name/trigger`, async (req, res) => {
|
|
6723
5499
|
try {
|
|
6724
|
-
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/trigger`, req.body, req.query, { request: req });
|
|
5500
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/trigger`, req.body, req.query, { request: req });
|
|
5501
|
+
sendResult(result, res);
|
|
5502
|
+
} catch (err) {
|
|
5503
|
+
errorResponse(err, res);
|
|
5504
|
+
}
|
|
5505
|
+
});
|
|
5506
|
+
server.post(`${base}/automation/:name/toggle`, async (req, res) => {
|
|
5507
|
+
try {
|
|
5508
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/toggle`, req.body, req.query, { request: req });
|
|
5509
|
+
sendResult(result, res);
|
|
5510
|
+
} catch (err) {
|
|
5511
|
+
errorResponse(err, res);
|
|
5512
|
+
}
|
|
5513
|
+
});
|
|
5514
|
+
server.get(`${base}/automation/:name/runs`, async (req, res) => {
|
|
5515
|
+
try {
|
|
5516
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs`, void 0, req.query, { request: req });
|
|
6725
5517
|
sendResult(result, res);
|
|
6726
5518
|
} catch (err) {
|
|
6727
5519
|
errorResponse(err, res);
|
|
6728
5520
|
}
|
|
6729
5521
|
});
|
|
6730
|
-
server.
|
|
5522
|
+
server.get(`${base}/automation/:name/runs/:runId`, async (req, res) => {
|
|
6731
5523
|
try {
|
|
6732
|
-
const result = await dispatcher.dispatch("
|
|
5524
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs/${req.params.runId}`, void 0, req.query, { request: req });
|
|
6733
5525
|
sendResult(result, res);
|
|
6734
5526
|
} catch (err) {
|
|
6735
5527
|
errorResponse(err, res);
|
|
6736
5528
|
}
|
|
6737
5529
|
});
|
|
6738
|
-
server.
|
|
5530
|
+
server.post(`${base}/automation/:name/runs/:runId/resume`, async (req, res) => {
|
|
6739
5531
|
try {
|
|
6740
|
-
const result = await dispatcher.dispatch("
|
|
5532
|
+
const result = await dispatcher.dispatch("POST", `/automation/${req.params.name}/runs/${req.params.runId}/resume`, req.body, req.query, { request: req });
|
|
6741
5533
|
sendResult(result, res);
|
|
6742
5534
|
} catch (err) {
|
|
6743
5535
|
errorResponse(err, res);
|
|
6744
5536
|
}
|
|
6745
5537
|
});
|
|
6746
|
-
server.get(`${base}/automation/:name/runs/:runId`, async (req, res) => {
|
|
5538
|
+
server.get(`${base}/automation/:name/runs/:runId/screen`, async (req, res) => {
|
|
6747
5539
|
try {
|
|
6748
|
-
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs/${req.params.runId}`, void 0, req.query, { request: req });
|
|
5540
|
+
const result = await dispatcher.dispatch("GET", `/automation/${req.params.name}/runs/${req.params.runId}/screen`, void 0, req.query, { request: req });
|
|
6749
5541
|
sendResult(result, res);
|
|
6750
5542
|
} catch (err) {
|
|
6751
5543
|
errorResponse(err, res);
|
|
@@ -7481,7 +6273,7 @@ var ArtifactApiClient = class {
|
|
|
7481
6273
|
};
|
|
7482
6274
|
|
|
7483
6275
|
// src/cloud/artifact-environment-registry.ts
|
|
7484
|
-
var
|
|
6276
|
+
var import_node_path5 = require("path");
|
|
7485
6277
|
var ArtifactEnvironmentRegistry = class {
|
|
7486
6278
|
constructor(config) {
|
|
7487
6279
|
this.hostnameCache = /* @__PURE__ */ new Map();
|
|
@@ -7653,7 +6445,7 @@ async function createDriver(driverType, databaseUrl, authToken) {
|
|
|
7653
6445
|
case "memory": {
|
|
7654
6446
|
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
7655
6447
|
const dbName = databaseUrl.replace(/^memory:\/\//, "").trim();
|
|
7656
|
-
const filePath = dbName ? (0,
|
|
6448
|
+
const filePath = dbName ? (0, import_node_path5.resolve)(process.cwd(), ".objectstack/data/projects", `${dbName}.json`) : void 0;
|
|
7657
6449
|
return new InMemoryDriver({
|
|
7658
6450
|
persistence: filePath ? { type: "file", path: filePath } : "file"
|
|
7659
6451
|
});
|
|
@@ -7691,21 +6483,17 @@ async function createDriver(driverType, databaseUrl, authToken) {
|
|
|
7691
6483
|
// src/cloud/artifact-kernel-factory.ts
|
|
7692
6484
|
var import_node_crypto2 = require("crypto");
|
|
7693
6485
|
var import_core3 = require("@objectstack/core");
|
|
7694
|
-
var
|
|
6486
|
+
var import_types3 = require("@objectstack/types");
|
|
7695
6487
|
init_driver_plugin();
|
|
7696
6488
|
init_app_plugin();
|
|
7697
6489
|
|
|
7698
6490
|
// src/cloud/capability-loader.ts
|
|
7699
6491
|
var CAPABILITY_PROVIDERS = {
|
|
7700
6492
|
automation: {
|
|
6493
|
+
// Self-contained: AutomationServicePlugin seeds all built-in node
|
|
6494
|
+
// executors itself (ADR-0018), so no companion node-pack plugins.
|
|
7701
6495
|
pkg: "@objectstack/service-automation",
|
|
7702
|
-
export: "AutomationServicePlugin"
|
|
7703
|
-
extras: [
|
|
7704
|
-
{ pkg: "@objectstack/service-automation", export: "CrudNodesPlugin" },
|
|
7705
|
-
{ pkg: "@objectstack/service-automation", export: "LogicNodesPlugin" },
|
|
7706
|
-
{ pkg: "@objectstack/service-automation", export: "HttpConnectorPlugin" },
|
|
7707
|
-
{ pkg: "@objectstack/service-automation", export: "ScreenNodesPlugin" }
|
|
7708
|
-
]
|
|
6496
|
+
export: "AutomationServicePlugin"
|
|
7709
6497
|
},
|
|
7710
6498
|
ai: {
|
|
7711
6499
|
pkg: "@objectstack/service-ai",
|
|
@@ -7736,6 +6524,19 @@ var CAPABILITY_PROVIDERS = {
|
|
|
7736
6524
|
pkg: "@objectstack/service-job",
|
|
7737
6525
|
export: "JobServicePlugin"
|
|
7738
6526
|
},
|
|
6527
|
+
messaging: {
|
|
6528
|
+
// Backs the `notify` flow node (ADR-0012): delivers to a user's
|
|
6529
|
+
// channels (inbox by default → `sys_inbox_message` rows).
|
|
6530
|
+
pkg: "@objectstack/service-messaging",
|
|
6531
|
+
export: "MessagingServicePlugin"
|
|
6532
|
+
},
|
|
6533
|
+
triggers: {
|
|
6534
|
+
// Concrete flow triggers — record-change (ObjectQL hooks) + schedule
|
|
6535
|
+
// (cron/interval via the job service; pair `triggers` with `job`).
|
|
6536
|
+
pkg: "@objectstack/plugin-trigger-record-change",
|
|
6537
|
+
export: "RecordChangeTriggerPlugin",
|
|
6538
|
+
extras: [{ pkg: "@objectstack/plugin-trigger-schedule", export: "ScheduleTriggerPlugin" }]
|
|
6539
|
+
},
|
|
7739
6540
|
realtime: {
|
|
7740
6541
|
pkg: "@objectstack/service-realtime",
|
|
7741
6542
|
export: "RealtimeServicePlugin"
|
|
@@ -7753,7 +6554,11 @@ async function loadCapabilities(opts) {
|
|
|
7753
6554
|
const { kernel, requires, bundle, environmentId } = opts;
|
|
7754
6555
|
const logger = opts.logger ?? console;
|
|
7755
6556
|
const installed = [];
|
|
7756
|
-
|
|
6557
|
+
const resolved = [...new Set(requires)];
|
|
6558
|
+
if (resolved.includes("audit") && !resolved.includes("messaging")) {
|
|
6559
|
+
resolved.push("messaging");
|
|
6560
|
+
}
|
|
6561
|
+
for (const cap of resolved) {
|
|
7757
6562
|
const spec = CAPABILITY_PROVIDERS[cap];
|
|
7758
6563
|
if (!spec) {
|
|
7759
6564
|
continue;
|
|
@@ -7820,8 +6625,197 @@ async function loadCapabilities(opts) {
|
|
|
7820
6625
|
return installed;
|
|
7821
6626
|
}
|
|
7822
6627
|
|
|
6628
|
+
// src/cloud/platform-sso.ts
|
|
6629
|
+
var import_node_crypto = require("crypto");
|
|
6630
|
+
var PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
|
|
6631
|
+
function derivePlatformSsoClientId(environmentId) {
|
|
6632
|
+
return `project_${environmentId}`;
|
|
6633
|
+
}
|
|
6634
|
+
function derivePlatformSsoClientSecret(baseSecret, environmentId) {
|
|
6635
|
+
return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
|
|
6636
|
+
}
|
|
6637
|
+
function hashPlatformSsoClientSecret(plaintext) {
|
|
6638
|
+
return (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
6639
|
+
}
|
|
6640
|
+
function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
|
|
6641
|
+
let host;
|
|
6642
|
+
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
|
|
6643
|
+
host = hostname;
|
|
6644
|
+
} else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
|
|
6645
|
+
const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
|
|
6646
|
+
const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
|
|
6647
|
+
host = `http://${hostWithPort}`;
|
|
6648
|
+
} else {
|
|
6649
|
+
host = `https://${hostname}`;
|
|
6650
|
+
}
|
|
6651
|
+
const trimmed = host.replace(/\/+$/, "");
|
|
6652
|
+
const path = basePath.replace(/\/+$/, "");
|
|
6653
|
+
return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
|
|
6654
|
+
}
|
|
6655
|
+
async function seedPlatformSsoClient(opts) {
|
|
6656
|
+
const { ql, environmentId, hostname, baseSecret, logger, throwOnError } = opts;
|
|
6657
|
+
if (!baseSecret) {
|
|
6658
|
+
logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { environmentId });
|
|
6659
|
+
return;
|
|
6660
|
+
}
|
|
6661
|
+
const clientId = derivePlatformSsoClientId(environmentId);
|
|
6662
|
+
const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, environmentId);
|
|
6663
|
+
const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
|
|
6664
|
+
const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
|
|
6665
|
+
let existing = null;
|
|
6666
|
+
try {
|
|
6667
|
+
const rows = await ql.find("sys_oauth_application", {
|
|
6668
|
+
where: { client_id: clientId },
|
|
6669
|
+
limit: 1
|
|
6670
|
+
}, { context: { isSystem: true } });
|
|
6671
|
+
const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6672
|
+
existing = list[0] ?? null;
|
|
6673
|
+
} catch (err) {
|
|
6674
|
+
logger?.warn?.("[platform-sso] sys_oauth_application read failed \u2014 skipping seed", {
|
|
6675
|
+
environmentId,
|
|
6676
|
+
error: err?.message
|
|
6677
|
+
});
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
6681
|
+
if (!existing) {
|
|
6682
|
+
const redirects = desiredRedirect ? [desiredRedirect] : [];
|
|
6683
|
+
try {
|
|
6684
|
+
await ql.insert("sys_oauth_application", {
|
|
6685
|
+
id: `oauthc_${environmentId}`,
|
|
6686
|
+
name: `Project ${environmentId}`,
|
|
6687
|
+
client_id: clientId,
|
|
6688
|
+
client_secret: clientSecretStored,
|
|
6689
|
+
type: "web",
|
|
6690
|
+
redirect_uris: JSON.stringify(redirects),
|
|
6691
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6692
|
+
response_types: JSON.stringify(["code"]),
|
|
6693
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6694
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
6695
|
+
require_pkce: false,
|
|
6696
|
+
skip_consent: true,
|
|
6697
|
+
disabled: false,
|
|
6698
|
+
subject_type: "public",
|
|
6699
|
+
created_at: nowIso,
|
|
6700
|
+
updated_at: nowIso
|
|
6701
|
+
}, { context: { isSystem: true } });
|
|
6702
|
+
logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
|
|
6703
|
+
} catch (err) {
|
|
6704
|
+
logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
|
|
6705
|
+
environmentId,
|
|
6706
|
+
error: err?.message
|
|
6707
|
+
});
|
|
6708
|
+
if (throwOnError) throw err;
|
|
6709
|
+
}
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
6712
|
+
let currentRedirects = [];
|
|
6713
|
+
try {
|
|
6714
|
+
const raw = existing.redirect_uris;
|
|
6715
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
6716
|
+
if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
|
|
6717
|
+
} catch {
|
|
6718
|
+
}
|
|
6719
|
+
const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
|
|
6720
|
+
const repairPatch = {
|
|
6721
|
+
name: existing.name || `Project ${environmentId}`,
|
|
6722
|
+
client_secret: clientSecretStored,
|
|
6723
|
+
type: existing.type || "web",
|
|
6724
|
+
redirect_uris: JSON.stringify(mergedRedirects),
|
|
6725
|
+
grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
|
|
6726
|
+
response_types: JSON.stringify(["code"]),
|
|
6727
|
+
scopes: JSON.stringify(["openid", "email", "profile"]),
|
|
6728
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
6729
|
+
require_pkce: false,
|
|
6730
|
+
skip_consent: true,
|
|
6731
|
+
disabled: false,
|
|
6732
|
+
subject_type: "public",
|
|
6733
|
+
updated_at: nowIso
|
|
6734
|
+
};
|
|
6735
|
+
try {
|
|
6736
|
+
await ql.update(
|
|
6737
|
+
"sys_oauth_application",
|
|
6738
|
+
repairPatch,
|
|
6739
|
+
{ where: { id: existing.id } },
|
|
6740
|
+
{ context: { isSystem: true } }
|
|
6741
|
+
);
|
|
6742
|
+
logger?.info?.("[platform-sso] sys_oauth_application repaired", {
|
|
6743
|
+
environmentId,
|
|
6744
|
+
clientId,
|
|
6745
|
+
redirect_uris: mergedRedirects
|
|
6746
|
+
});
|
|
6747
|
+
} catch (err) {
|
|
6748
|
+
logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
|
|
6749
|
+
environmentId,
|
|
6750
|
+
error: err?.message
|
|
6751
|
+
});
|
|
6752
|
+
if (throwOnError) throw err;
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
async function backfillPlatformSsoClients(opts) {
|
|
6756
|
+
const { ql, baseSecret, logger, limit = 1e3 } = opts;
|
|
6757
|
+
if (!baseSecret) {
|
|
6758
|
+
logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
|
|
6759
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
|
|
6760
|
+
}
|
|
6761
|
+
let projects = [];
|
|
6762
|
+
try {
|
|
6763
|
+
const rows = await ql.find("sys_environment", {
|
|
6764
|
+
limit,
|
|
6765
|
+
fields: ["id", "hostname", "status"]
|
|
6766
|
+
}, { context: { isSystem: true } });
|
|
6767
|
+
projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
|
|
6768
|
+
} catch (err) {
|
|
6769
|
+
logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
|
|
6770
|
+
error: err?.message
|
|
6771
|
+
});
|
|
6772
|
+
return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
|
|
6773
|
+
}
|
|
6774
|
+
let seeded = 0;
|
|
6775
|
+
let alreadyExisted = 0;
|
|
6776
|
+
const failures = [];
|
|
6777
|
+
for (const p of projects) {
|
|
6778
|
+
if (!p?.id) continue;
|
|
6779
|
+
const before = await (async () => {
|
|
6780
|
+
try {
|
|
6781
|
+
const r = await ql.find("sys_oauth_application", {
|
|
6782
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6783
|
+
limit: 1
|
|
6784
|
+
}, { context: { isSystem: true } });
|
|
6785
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6786
|
+
return list[0] ?? null;
|
|
6787
|
+
} catch {
|
|
6788
|
+
return null;
|
|
6789
|
+
}
|
|
6790
|
+
})();
|
|
6791
|
+
try {
|
|
6792
|
+
await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
|
|
6793
|
+
if (before) alreadyExisted++;
|
|
6794
|
+
else {
|
|
6795
|
+
const after = await (async () => {
|
|
6796
|
+
try {
|
|
6797
|
+
const r = await ql.find("sys_oauth_application", {
|
|
6798
|
+
where: { client_id: derivePlatformSsoClientId(p.id) },
|
|
6799
|
+
limit: 1
|
|
6800
|
+
}, { context: { isSystem: true } });
|
|
6801
|
+
const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
|
|
6802
|
+
return list[0] ?? null;
|
|
6803
|
+
} catch (err) {
|
|
6804
|
+
return { _readErr: err?.message };
|
|
6805
|
+
}
|
|
6806
|
+
})();
|
|
6807
|
+
if (after && !after._readErr) seeded++;
|
|
6808
|
+
else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
|
|
6809
|
+
}
|
|
6810
|
+
} catch (err) {
|
|
6811
|
+
failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6814
|
+
logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
|
|
6815
|
+
return { scanned: projects.length, seeded, alreadyExisted, failures };
|
|
6816
|
+
}
|
|
6817
|
+
|
|
7823
6818
|
// src/cloud/artifact-kernel-factory.ts
|
|
7824
|
-
init_platform_sso();
|
|
7825
6819
|
function deriveProjectAuthSecret(baseSecret, environmentId) {
|
|
7826
6820
|
return (0, import_node_crypto2.createHmac)("sha256", baseSecret).update(`project:${environmentId}`).digest("hex");
|
|
7827
6821
|
}
|
|
@@ -7831,7 +6825,7 @@ var ArtifactKernelFactory = class {
|
|
|
7831
6825
|
this.envRegistry = config.envRegistry;
|
|
7832
6826
|
this.logger = config.logger ?? console;
|
|
7833
6827
|
this.kernelConfig = config.kernelConfig;
|
|
7834
|
-
this.authBaseSecret = (config.authBaseSecret ?? (0,
|
|
6828
|
+
this.authBaseSecret = (config.authBaseSecret ?? (0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
7835
6829
|
}
|
|
7836
6830
|
async create(environmentId) {
|
|
7837
6831
|
let cached = this.envRegistry.peekById(environmentId);
|
|
@@ -7944,7 +6938,7 @@ var ArtifactKernelFactory = class {
|
|
|
7944
6938
|
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
|
|
7945
6939
|
}
|
|
7946
6940
|
try {
|
|
7947
|
-
const multiTenant = String((0,
|
|
6941
|
+
const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
7948
6942
|
if (multiTenant) {
|
|
7949
6943
|
try {
|
|
7950
6944
|
const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
|
|
@@ -8470,7 +7464,7 @@ var AuthProxyPlugin = class {
|
|
|
8470
7464
|
};
|
|
8471
7465
|
|
|
8472
7466
|
// src/cloud/cloud-url.ts
|
|
8473
|
-
var DEFAULT_CLOUD_URL = "https://cloud.objectos.
|
|
7467
|
+
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
8474
7468
|
function resolveCloudUrl(explicit) {
|
|
8475
7469
|
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
8476
7470
|
const lower = raw.toLowerCase();
|
|
@@ -8892,11 +7886,11 @@ var RuntimeConfigPlugin = class {
|
|
|
8892
7886
|
|
|
8893
7887
|
// src/cloud/file-artifact-api-client.ts
|
|
8894
7888
|
var import_promises2 = require("fs/promises");
|
|
8895
|
-
var
|
|
7889
|
+
var import_node_path6 = require("path");
|
|
8896
7890
|
var FileArtifactApiClient = class {
|
|
8897
7891
|
constructor(config = {}) {
|
|
8898
7892
|
const cwd = process.cwd();
|
|
8899
|
-
this.artifactPath = (0,
|
|
7893
|
+
this.artifactPath = (0, import_node_path6.resolve)(
|
|
8900
7894
|
cwd,
|
|
8901
7895
|
config.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? "dist/objectstack.json"
|
|
8902
7896
|
);
|
|
@@ -8992,7 +7986,7 @@ var FileArtifactApiClient = class {
|
|
|
8992
7986
|
}
|
|
8993
7987
|
defaultLocalSqliteRuntime() {
|
|
8994
7988
|
const cwd = process.cwd();
|
|
8995
|
-
const dbPath = (0,
|
|
7989
|
+
const dbPath = (0, import_node_path6.resolve)(cwd, ".objectstack/data", `${this.environmentId}.db`);
|
|
8996
7990
|
return {
|
|
8997
7991
|
databaseDriver: "sqlite",
|
|
8998
7992
|
databaseUrl: `file:${dbPath}`
|
|
@@ -9145,9 +8139,9 @@ async function createObjectOSStack(config) {
|
|
|
9145
8139
|
}
|
|
9146
8140
|
|
|
9147
8141
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
9148
|
-
var
|
|
9149
|
-
var
|
|
9150
|
-
var
|
|
8142
|
+
var import_node_fs4 = require("fs");
|
|
8143
|
+
var import_node_path7 = require("path");
|
|
8144
|
+
var import_types4 = require("@objectstack/types");
|
|
9151
8145
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
9152
8146
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
9153
8147
|
function safeFilename(manifestId) {
|
|
@@ -9222,9 +8216,6 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9222
8216
|
}
|
|
9223
8217
|
};
|
|
9224
8218
|
this.handleInstall = async (c, ctx) => {
|
|
9225
|
-
if (!this.cloudUrl) {
|
|
9226
|
-
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
9227
|
-
}
|
|
9228
8219
|
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
9229
8220
|
if (!userId) {
|
|
9230
8221
|
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required to install packages." } }, 401);
|
|
@@ -9234,69 +8225,87 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9234
8225
|
body = await c.req.json();
|
|
9235
8226
|
} catch {
|
|
9236
8227
|
}
|
|
9237
|
-
const
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
8228
|
+
const inlineManifest = body?.manifest && typeof body.manifest === "object" ? body.manifest : null;
|
|
8229
|
+
let manifest;
|
|
8230
|
+
let resolvedVersionId;
|
|
8231
|
+
let version;
|
|
8232
|
+
let packageId;
|
|
8233
|
+
if (inlineManifest) {
|
|
8234
|
+
manifest = inlineManifest;
|
|
8235
|
+
packageId = String(manifest.id ?? manifest.name ?? "").trim();
|
|
8236
|
+
version = String(manifest.version ?? "unknown");
|
|
8237
|
+
resolvedVersionId = String(body?.versionId ?? version);
|
|
8238
|
+
if (!packageId) {
|
|
8239
|
+
return c.json({ success: false, error: { code: "invalid_manifest", message: 'Inline manifest must have an "id" or "name".' } }, 400);
|
|
8240
|
+
}
|
|
8241
|
+
} else {
|
|
8242
|
+
if (!this.cloudUrl) {
|
|
8243
|
+
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
8244
|
+
}
|
|
8245
|
+
packageId = String(body?.packageId ?? "").trim();
|
|
8246
|
+
const versionId = String(body?.versionId ?? "latest").trim() || "latest";
|
|
8247
|
+
if (!packageId) {
|
|
8248
|
+
return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
|
|
8249
|
+
}
|
|
8250
|
+
let payload;
|
|
8251
|
+
const publicBase = resolveMarketplacePublicBaseUrl();
|
|
8252
|
+
const fetchAttempts = [];
|
|
8253
|
+
if (publicBase) {
|
|
8254
|
+
fetchAttempts.push({
|
|
8255
|
+
label: "public-r2",
|
|
8256
|
+
url: `${publicBase}/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest.json`
|
|
8257
|
+
});
|
|
8258
|
+
}
|
|
9246
8259
|
fetchAttempts.push({
|
|
9247
|
-
label: "
|
|
9248
|
-
url: `${
|
|
8260
|
+
label: "cloud",
|
|
8261
|
+
url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
|
|
9249
8262
|
});
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
8263
|
+
let lastErrStatus = 0;
|
|
8264
|
+
let lastErrText = "";
|
|
8265
|
+
for (const attempt of fetchAttempts) {
|
|
8266
|
+
try {
|
|
8267
|
+
const resp = await fetch(attempt.url, { headers: { "Accept": "application/json" } });
|
|
8268
|
+
if (!resp.ok) {
|
|
8269
|
+
lastErrStatus = resp.status;
|
|
8270
|
+
lastErrText = (await resp.text().catch(() => "")).slice(0, 200);
|
|
8271
|
+
if (attempt.label === "public-r2" && resp.status === 404) {
|
|
8272
|
+
ctx.logger?.info?.(`[MarketplaceInstallLocal] public-r2 miss for ${packageId}@${versionId}, falling back to cloud`);
|
|
8273
|
+
continue;
|
|
8274
|
+
}
|
|
8275
|
+
if (attempt.label === "public-r2" && resp.status >= 500) {
|
|
8276
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 ${resp.status}, falling back to cloud`);
|
|
8277
|
+
continue;
|
|
8278
|
+
}
|
|
8279
|
+
break;
|
|
9266
8280
|
}
|
|
9267
|
-
|
|
9268
|
-
|
|
8281
|
+
payload = await resp.json();
|
|
8282
|
+
lastErrStatus = 0;
|
|
8283
|
+
break;
|
|
8284
|
+
} catch (err) {
|
|
8285
|
+
if (attempt.label === "public-r2") {
|
|
8286
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
9269
8287
|
continue;
|
|
9270
8288
|
}
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
break;
|
|
9276
|
-
} catch (err) {
|
|
9277
|
-
if (attempt.label === "public-r2") {
|
|
9278
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
9279
|
-
continue;
|
|
8289
|
+
return c.json({
|
|
8290
|
+
success: false,
|
|
8291
|
+
error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
|
|
8292
|
+
}, 502);
|
|
9280
8293
|
}
|
|
8294
|
+
}
|
|
8295
|
+
if (!payload) {
|
|
9281
8296
|
return c.json({
|
|
9282
8297
|
success: false,
|
|
9283
|
-
error: { code: "cloud_fetch_failed", message:
|
|
9284
|
-
}, 502);
|
|
8298
|
+
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
8299
|
+
}, lastErrStatus === 404 ? 404 : 502);
|
|
9285
8300
|
}
|
|
8301
|
+
const data = payload?.data ?? payload;
|
|
8302
|
+
manifest = data?.manifest;
|
|
8303
|
+
resolvedVersionId = String(data?.version_id ?? versionId);
|
|
8304
|
+
version = String(data?.version ?? "unknown");
|
|
9286
8305
|
}
|
|
9287
|
-
if (!payload) {
|
|
9288
|
-
return c.json({
|
|
9289
|
-
success: false,
|
|
9290
|
-
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
9291
|
-
}, lastErrStatus === 404 ? 404 : 502);
|
|
9292
|
-
}
|
|
9293
|
-
const data = payload?.data ?? payload;
|
|
9294
|
-
const manifest = data?.manifest;
|
|
9295
|
-
const resolvedVersionId = String(data?.version_id ?? versionId);
|
|
9296
|
-
const version = String(data?.version ?? "unknown");
|
|
9297
8306
|
const manifestId = String(manifest?.id ?? manifest?.name ?? "");
|
|
9298
8307
|
if (!manifest || !manifestId) {
|
|
9299
|
-
return c.json({ success: false, error: { code: "invalid_manifest", message: "
|
|
8308
|
+
return c.json({ success: false, error: { code: "invalid_manifest", message: "Invalid manifest payload." } }, inlineManifest ? 400 : 502);
|
|
9300
8309
|
}
|
|
9301
8310
|
const conflict = this.findConflict(ctx, manifestId);
|
|
9302
8311
|
if (conflict === "user-code") {
|
|
@@ -9308,6 +8317,18 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9308
8317
|
}
|
|
9309
8318
|
}, 409);
|
|
9310
8319
|
}
|
|
8320
|
+
try {
|
|
8321
|
+
const manifestService = ctx.getService("manifest");
|
|
8322
|
+
manifestService.register(manifest);
|
|
8323
|
+
} catch (err) {
|
|
8324
|
+
if (inlineManifest) {
|
|
8325
|
+
return c.json({
|
|
8326
|
+
success: false,
|
|
8327
|
+
error: { code: "register_failed", message: `Failed to register imported manifest: ${err?.message ?? err}` }
|
|
8328
|
+
}, 422);
|
|
8329
|
+
}
|
|
8330
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
8331
|
+
}
|
|
9311
8332
|
const entry = {
|
|
9312
8333
|
packageId,
|
|
9313
8334
|
versionId: resolvedVersionId,
|
|
@@ -9319,20 +8340,14 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9319
8340
|
withSampleData: false
|
|
9320
8341
|
};
|
|
9321
8342
|
try {
|
|
9322
|
-
(0,
|
|
9323
|
-
(0,
|
|
8343
|
+
(0, import_node_fs4.mkdirSync)(this.storageDir, { recursive: true });
|
|
8344
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9324
8345
|
} catch (err) {
|
|
9325
8346
|
return c.json({
|
|
9326
8347
|
success: false,
|
|
9327
8348
|
error: { code: "storage_failed", message: `Failed to persist manifest: ${err?.message ?? err}` }
|
|
9328
8349
|
}, 500);
|
|
9329
8350
|
}
|
|
9330
|
-
try {
|
|
9331
|
-
const manifestService = ctx.getService("manifest");
|
|
9332
|
-
manifestService.register(manifest);
|
|
9333
|
-
} catch (err) {
|
|
9334
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
9335
|
-
}
|
|
9336
8351
|
try {
|
|
9337
8352
|
const ql = ctx.getService("objectql");
|
|
9338
8353
|
if (ql && typeof ql.syncSchemas === "function") {
|
|
@@ -9346,7 +8361,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9346
8361
|
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
9347
8362
|
entry.withSampleData = true;
|
|
9348
8363
|
try {
|
|
9349
|
-
(0,
|
|
8364
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9350
8365
|
} catch {
|
|
9351
8366
|
}
|
|
9352
8367
|
}
|
|
@@ -9393,12 +8408,12 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9393
8408
|
if (!manifestId) {
|
|
9394
8409
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9395
8410
|
}
|
|
9396
|
-
const file = (0,
|
|
9397
|
-
if (!(0,
|
|
8411
|
+
const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
|
|
8412
|
+
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
9398
8413
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9399
8414
|
}
|
|
9400
8415
|
try {
|
|
9401
|
-
(0,
|
|
8416
|
+
(0, import_node_fs4.unlinkSync)(file);
|
|
9402
8417
|
} catch (err) {
|
|
9403
8418
|
return c.json({ success: false, error: { code: "storage_failed", message: err?.message ?? String(err) } }, 500);
|
|
9404
8419
|
}
|
|
@@ -9421,7 +8436,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9421
8436
|
* (refuse to avoid silently overwriting authored code)
|
|
9422
8437
|
*/
|
|
9423
8438
|
this.findConflict = (ctx, manifestId) => {
|
|
9424
|
-
if ((0,
|
|
8439
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)))) {
|
|
9425
8440
|
return "marketplace";
|
|
9426
8441
|
}
|
|
9427
8442
|
try {
|
|
@@ -9463,13 +8478,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9463
8478
|
if (!manifestId) {
|
|
9464
8479
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9465
8480
|
}
|
|
9466
|
-
const file = (0,
|
|
9467
|
-
if (!(0,
|
|
8481
|
+
const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
|
|
8482
|
+
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
9468
8483
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9469
8484
|
}
|
|
9470
8485
|
let entry;
|
|
9471
8486
|
try {
|
|
9472
|
-
entry = JSON.parse((0,
|
|
8487
|
+
entry = JSON.parse((0, import_node_fs4.readFileSync)(file, "utf8"));
|
|
9473
8488
|
} catch (err) {
|
|
9474
8489
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9475
8490
|
}
|
|
@@ -9485,7 +8500,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9485
8500
|
}
|
|
9486
8501
|
try {
|
|
9487
8502
|
entry.withSampleData = true;
|
|
9488
|
-
(0,
|
|
8503
|
+
(0, import_node_fs4.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9489
8504
|
} catch {
|
|
9490
8505
|
}
|
|
9491
8506
|
return c.json({
|
|
@@ -9517,13 +8532,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9517
8532
|
if (!manifestId) {
|
|
9518
8533
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9519
8534
|
}
|
|
9520
|
-
const file = (0,
|
|
9521
|
-
if (!(0,
|
|
8535
|
+
const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
|
|
8536
|
+
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
9522
8537
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9523
8538
|
}
|
|
9524
8539
|
let entry;
|
|
9525
8540
|
try {
|
|
9526
|
-
entry = JSON.parse((0,
|
|
8541
|
+
entry = JSON.parse((0, import_node_fs4.readFileSync)(file, "utf8"));
|
|
9527
8542
|
} catch (err) {
|
|
9528
8543
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9529
8544
|
}
|
|
@@ -9572,7 +8587,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9572
8587
|
}
|
|
9573
8588
|
try {
|
|
9574
8589
|
entry.withSampleData = false;
|
|
9575
|
-
(0,
|
|
8590
|
+
(0, import_node_fs4.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9576
8591
|
} catch {
|
|
9577
8592
|
}
|
|
9578
8593
|
ctx.logger?.info?.(`[MarketplaceInstallLocal] purged ${manifestId}: deleted=${deleted} skipped=${skipped} errors=${errors}`);
|
|
@@ -9670,7 +8685,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9670
8685
|
}
|
|
9671
8686
|
}
|
|
9672
8687
|
if (opts.seedNow && datasets.length > 0) {
|
|
9673
|
-
const multiTenant = String((0,
|
|
8688
|
+
const multiTenant = String((0, import_types4.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
9674
8689
|
try {
|
|
9675
8690
|
const ql = ctx.getService("objectql");
|
|
9676
8691
|
let metadata;
|
|
@@ -9771,12 +8786,12 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9771
8786
|
return null;
|
|
9772
8787
|
};
|
|
9773
8788
|
this.readAll = () => {
|
|
9774
|
-
if (!(0,
|
|
8789
|
+
if (!(0, import_node_fs4.existsSync)(this.storageDir)) return [];
|
|
9775
8790
|
const out = [];
|
|
9776
|
-
for (const name of (0,
|
|
8791
|
+
for (const name of (0, import_node_fs4.readdirSync)(this.storageDir)) {
|
|
9777
8792
|
if (!name.endsWith(".json")) continue;
|
|
9778
8793
|
try {
|
|
9779
|
-
const raw = (0,
|
|
8794
|
+
const raw = (0, import_node_fs4.readFileSync)((0, import_node_path7.join)(this.storageDir, name), "utf8");
|
|
9780
8795
|
out.push(JSON.parse(raw));
|
|
9781
8796
|
} catch {
|
|
9782
8797
|
}
|
|
@@ -9784,13 +8799,10 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9784
8799
|
return out;
|
|
9785
8800
|
};
|
|
9786
8801
|
this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
|
|
9787
|
-
this.storageDir = config.storageDir ? (0,
|
|
8802
|
+
this.storageDir = config.storageDir ? (0, import_node_path7.resolve)(config.storageDir) : (0, import_node_path7.resolve)(process.cwd(), DEFAULT_DIR);
|
|
9788
8803
|
}
|
|
9789
8804
|
};
|
|
9790
8805
|
|
|
9791
|
-
// src/index.ts
|
|
9792
|
-
init_platform_sso();
|
|
9793
|
-
|
|
9794
8806
|
// src/sandbox/script-runner.ts
|
|
9795
8807
|
var UnimplementedScriptRunner = class {
|
|
9796
8808
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -9816,7 +8828,7 @@ init_body_runner();
|
|
|
9816
8828
|
// src/index.ts
|
|
9817
8829
|
var import_rest = require("@objectstack/rest");
|
|
9818
8830
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
9819
|
-
var
|
|
8831
|
+
var import_types5 = require("@objectstack/types");
|
|
9820
8832
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9821
8833
|
0 && (module.exports = {
|
|
9822
8834
|
AppPlugin,
|
|
@@ -9827,6 +8839,7 @@ var import_types6 = require("@objectstack/types");
|
|
|
9827
8839
|
DEFAULT_CLOUD_URL,
|
|
9828
8840
|
DEFAULT_RATE_LIMITS,
|
|
9829
8841
|
DriverPlugin,
|
|
8842
|
+
ExternalValidationPlugin,
|
|
9830
8843
|
FileArtifactApiClient,
|
|
9831
8844
|
HttpDispatcher,
|
|
9832
8845
|
HttpServer,
|
|
@@ -9865,6 +8878,7 @@ var import_types6 = require("@objectstack/types");
|
|
|
9865
8878
|
collectBundleHooks,
|
|
9866
8879
|
createDefaultHostConfig,
|
|
9867
8880
|
createDispatcherPlugin,
|
|
8881
|
+
createExternalValidationPlugin,
|
|
9868
8882
|
createObjectOSStack,
|
|
9869
8883
|
createRestApiPlugin,
|
|
9870
8884
|
createStandaloneStack,
|